netcore web api 签名封装(中间件统一签名&方法签名)

分类: NetCore

为了不再裸奔API,我们进行了统一的签名封装,当然这是小项目使用没问题,如果我们有很多微服务,那么建议在网关层统一进行签名验证。

解决以下问题:

  1. 防止非法请求(请求来源合法性验证)
  2. 防止重复请求(请求唯一性,利用随机码+时间戳防止重放攻击)
  3. 防止请求参数被篡改

这里提供两种方式进行签名,方便公共使用。

当然如果自己有其他的业务需求可以自行拿取源码修改
具体原由没必要再去将,我们直接实现

源码:Web Api Sign

签名算法规则

1、拼接appid,timestamp,noncestr字符串,并按照字符进行ASCII排序,得到字符串:appid={appid}&timestamp={10位时间戳}&noncestr={16位随机字符串}

2、将appSecret追加到拼接串后面,得到字符串:appid={appid}&timestamp={10位时间戳}&noncestr={16位随机字符串}&key={appSecret}

3、将其字符串MD5 32位 加密,然后全部小写 ToLower

4、然后进行 Sha256(md5+appSecret).ToLower() 加密后,得到签名

5、公式:Sha256(md5(appid={appid}&timestamp={10位时间戳}&noncestr={16位随机字符串}&key={appSecret})+{appSecret})

方法签名

适用于针对性的个别API进行签名验证

 

使用样例:

    public class HttpSignApiAttribute : HttpSignAttribute
    {
        /// <summary>
        /// 获取密钥
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="appid"></param>
        /// <returns></returns>
        public override Task<string> GetAppSecretAsync(IServiceProvider serviceProvider, string appid)
        {
            appid = "web1ed21e4udroo37fmj";

            return Task.FromResult("CdzL5v9s6cmYOqeYW2ZicfdTaT3LdXhJ");
        }
        /// <summary>
        /// 重放处理
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="appid"></param>
        /// <param name="timestamp"></param>
        /// <param name="noncestr"></param>
        /// <returns></returns>
        public override Task<bool> PreventReplayAttackAsync(IServiceProvider serviceProvider, string appid, string timestamp, string noncestr)
        {
            return base.PreventReplayAttackAsync(serviceProvider, appid, timestamp, noncestr);
        }
    }

 

在需要验证的API上设置头部标签

        [HttpSignApi]
        [HttpGet]
        [AllowAnonymous]
        public Result<User> Get()
        {
            return new Result<User>(0, "成功", new User { Id = 1, Name = "test", CreateTime = DateTime.Now });
        }

 

中间件统一实现

具体实现代码是一致的

    /// <summary>
    /// http签名中间件
    /// <para>签名算法:</para>
    /// <para>1、拼接appid,timestamp,noncestr字符串,并按照字符进行ASCII排序,得到字符串:appid={appid}&amp;timestamp={10位时间戳}&amp;noncestr={16位随机字符串}</para>
    /// <para>2、将appSecret追加到拼接串后面,得到字符串:appid={appid}&amp;timestamp={10位时间戳}&amp;noncestr={16位随机字符串}&amp;key={appSecret}</para>
    /// <para>3、将期字符串MD5 32位 加密,然后全部小写 ToLower</para>
    /// <para>4、然后进行 Sha256(md5+appSecret).ToLower() 加密后,得到签名</para>
    /// <para>公式:Sha256(md5(appid={appid}&amp;timestamp={10位时间戳}&amp;noncestr={16位随机字符串}&amp;key={appSecret})+{appSecret})</para>
    /// </summary>
    public abstract class HttpSignMiddleware : Middlewares.IMiddleware
    {
        /// <summary>
        /// 方法
        /// </summary>
        private readonly RequestDelegate _next;

        /// <summary>
        /// 签名选项
        /// </summary>
        private readonly HttpSignOptions _options;

        /// <summary>
        /// 初始化一个<see cref="HttpSignMiddleware"/>类型的实例
        /// <para>签名算法:</para>
        /// <para>1、拼接appid,timestamp,noncestr字符串,并按照字符进行ASCII排序,得到字符串:appid={appid}&amp;timestamp={10位时间戳}&amp;noncestr={16位随机字符串}</para>
        /// <para>2、将appSecret追加到拼接串后面,得到字符串:appid={appid}&amp;timestamp={10位时间戳}&amp;noncestr={16位随机字符串}&amp;key={appSecret}</para>
        /// <para>3、将期字符串MD5 32位 加密,然后全部小写 ToLower</para>
        /// <para>4、然后进行 Sha256(md5+appSecret).ToLower() 加密后,得到签名</para>
        /// <para>公式:Sha256(md5(appid={appid}&amp;timestamp={10位时间戳}&amp;noncestr={16位随机字符串}&amp;key={appSecret})+{appSecret})</para>
        /// </summary>
        /// <param name="next">方法</param>
        /// <param name="options">真实IP选项</param>
        public HttpSignMiddleware(RequestDelegate next, IOptions<HttpSignOptions> options)
        {
            _next = next;
            _options = options.Value;
        }

        public async Task Invoke(HttpContext context)
        {
            var path = context.Request.Path.Value;

            if (!_options.IsOpen || path.IndexOf("/swagger") > -1)
            {
                await _next.Invoke(context);
                return;
            }

            var headers = context.Request.Headers;

            string appid = headers[$"{_options.Prefix}appid"].SafeString();
            string sign = headers[$"{_options.Prefix}sign"].SafeString();
            string timestamp = headers[$"{_options.Prefix}timestamp"].SafeString();
            string noncestr = headers[$"{_options.Prefix}noncestr"].SafeString();

            if (appid.IsEmpty() || sign.IsEmpty() || timestamp.IsEmpty() || noncestr.IsEmpty())
            {
                await WriteAsync(context, HttpSignSubCode.MissingParams);

                return;
            }

            if (timestamp.Length != 10 || noncestr.Length != 16)
            {
                await WriteAsync(context, HttpSignSubCode.ParamsFail);

                return;
            }

            var nowTimestamp = DateTime.Now.ToTimeStamp(false);

            if (nowTimestamp - timestamp.ToLong() > _options.TimeOut)
            {
                await WriteAsync(context, HttpSignSubCode.RequestTimeout);

                return;
            }

            var appSecret = await GetAppSecretAsync(appid);

            if (!(await CreateSignAsync(appid, appSecret, timestamp, noncestr)).Equals(sign))
            {
                await WriteAsync(context, HttpSignSubCode.SignFail);

                return;
            }

            if (!(await PreventReplayAttackAsync(appid, timestamp, noncestr)))
            {
                await WriteAsync(context, HttpSignSubCode.RequestFail);

                return;
            }

            await _next.Invoke(context);
        }
        /// <summary>
        /// 获取appSecret密钥
        /// </summary>
        /// <param name="appid"></param>
        /// <returns></returns>
        public abstract Task<string> GetAppSecretAsync(string appid);
        /// <summary>
        /// 防止重放攻击
        /// </summary>
        /// <param name="appid"></param>
        /// <param name="timestamp"></param>
        /// <param name="noncestr"></param>
        /// <returns></returns>
        public virtual async Task<bool> PreventReplayAttackAsync(string appid, string timestamp, string noncestr) => await Task.FromResult(true);
        /// <summary>
        /// 创建签名
        /// </summary>
        /// <param name="appid"></param>
        /// <param name="appSecret"></param>
        /// <param name="timestamp"></param>
        /// <param name="noncestr"></param>
        /// <returns></returns>
        public virtual async Task<string> CreateSignAsync(string appid, string appSecret, string timestamp, string noncestr)
        {
            await Task.CompletedTask;

            return
                SignParameters.Create()
                    .Add("appid", appid)
                    .Add("timestamp", timestamp)
                    .Add("noncestr", noncestr)
                    .CreateSign("key", appSecret);
        }
        /// <summary>
        /// 输出信息
        /// </summary>
        /// <param name="context"></param>
        /// <param name="subCode"></param>
        /// <returns></returns>
        private async Task WriteAsync(HttpContext context, HttpSignSubCode subCode)
        {
            context.Response.ContentType = "application/json";

            context.Response.StatusCode = (int)HttpStatusCode.OK;

            var (code, message) = HttpSignCode.Message(subCode);

            await context.Response.WriteAsync(new Result<string>(StateCode.Fail, code, message, "").ToJson(), Encoding.UTF8);
        }
    }

 

使用方法:

public class SignMiddlewareDemo : HttpSignMiddleware
    {
        public SignMiddlewareDemo(RequestDelegate next, IOptions<HttpSignOptions> options)
            : base(next, options)
        {

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="appid"></param>
        /// <returns></returns>
        public override Task<string> GetAppSecretAsync(string appid)
        {
            appid = "web1ed21e4udroo37fmj";

            return Task.FromResult("CdzL5v9s6cmYOqeYW2ZicfdTaT3LdXhJ");
        }
        /// <summary>
        /// 重放处理
        /// </summary>
        /// <param name="appid"></param>
        /// <param name="timestamp"></param>
        /// <param name="noncestr"></param>
        /// <returns></returns>
        public override Task<bool> PreventReplayAttackAsync(string appid, string timestamp, string noncestr)
        {
            return base.PreventReplayAttackAsync(appid, timestamp, noncestr);
        }
    }

在Startup的Configure中注册中间件

app.UseHttpSign<SignMiddlewareDemo>();

 

最后,为了完美配合web api,在swagger文档里肯定是需要加上参数的

我们可以注册的时候增加 AddHttpSignDoc 在swagger上增加header参数。

services.AddMiniSwagger(swaggerGenAction: (opt) =>
        {
            opt.SwaggerDoc("test", new OpenApiInfo
            {
                Version = "v1.0.0",
                Title = $"test",
                Description = "test"
            });

            opt.AddJwtBearerDoc();
            opt.AddHttpSignDoc(services);
            opt.AddDescriptions(typeof(Program), "XUCore.NetCore.ApiTests.xml");

            // TODO:一定要返回true!
            opt.DocInclusionPredicate((docName, description) => true);
        });

 

标签: NetCore

上一篇: codefirst和databasefirst操作

下一篇: netcore 跨域配置

by 2023-08-07 23:49:07
篇笔记

学习笔记