• Api接口签名验证


    通过特性来统一验证的入口,实现ActionFilterAttribute接口来进行接口的签名验证

        /// <summary>
        /// 标准接口基类Controller
        /// </summary>
        [SignVerification]
        public abstract class BaseApiController : Controller
        {
        }
        
        /// <summary>
        /// 接口签名验证
        /// </summary>
        public class SignVerificationAttribute : ActionFilterAttribute,IAuthenticationFilter
        {
        }

    实现的思路为:

    1.不同对接方的接口(插件)定义不同的验证key,不同的插件间不能混用验证key

    2.不同的插件生成不同的partnerId,partnerKey。请求的Url中需要携带partnerId,通过partnerId作为key在redis中找到对应的插件验证信息(包括:partnerId,partnerKey等)

    3.Url参数中必须包含partnerId,ts(时间戳),sign(加密签名)。ts时间戳的有效时间为5分钟,sign为(时间戳:formBody:partnerId:partnerKey)的MD5加密

    4.如果通过partnerId可以找到对应的验证信息,再把(时间戳:formBody:partnerId:partnerKey)MD5加密后和sign比较确保请求没有被篡改

    5.确保partnerId为当前插件而非其他插件的,因为redis是共用的,只是通过key去取值而已

    签名方式

    将时间戳和请求Form参数以及PartnerKey以冒号连接,如(时间戳:body:partnerId:PartnerKey)
    将连接好的字符串进行MD5生成sign

    Url参数

    参数说明类型必须备注
    pid partnerId string  
    ts 时间戳(格式:yyyyMMddHHmmss) string 时间戳的有效时间为5分钟
    sign MD5(时间戳:body:partnerId:pkey) string 参考签名方式

    具体代码实现

        /// <summary>
        /// 接口签名验证
        /// </summary>
        public class SignVerificationAttribute : ActionFilterAttribute, IAuthenticationFilter
        {
            private readonly IDefaultUserService _defaultUserService;
            private readonly IInterfaceSignProvider _interfaceSignProvider;
            public SignVerificationAttribute()
            {
                _defaultUserService = ObjectContainer.GetService<IDefaultUserService>();
                _interfaceSignProvider = ObjectContainer.GetService<IInterfaceSignProvider>();
            }
    
            public void OnAuthentication(AuthenticationContext filterContext)
            {
                var request = filterContext.HttpContext.Request;
                var partnerId = request.QueryString["pid"];
                var timeStamp = request.QueryString["ts"];
                var sign = request.QueryString["sign"];//获取Url参数
                var body = GetBodyText(request.InputStream);
    
                if (!ValidSign(filterContext,timeStamp, sign, body,partnerId,out IInterfaceSignInfo signInfo))//加密验证
                {
                    filterContext.Result = new ApiResult {Success = false, ErrorMessage = "无效签名"};
                    return;
                }
    
                var service = ObjectContainer.GetService<IAuthenticationService>();
                var userId = _defaultUserService.GetDefaultUserId(signInfo.LicNo);
                var identity = service.SignIn(userId, signInfo.LicNo, false, TimeSpan.FromMinutes(5), SessionType.WebApi);
                var newPrincipal = new GenericPrincipal(identity, new string[] { });
                filterContext.Principal = newPrincipal;
            }
            private static string GetBodyText(Stream stream)
            {
                using (var ms = new MemoryStream())
                {
                    stream.CopyTo(ms);
                    return Encoding.UTF8.GetString(ms.ToArray());
                }
            }
    
            private bool ValidSign(AuthenticationContext filterContext,string timeStamp, string sign, string body,string partnerId,out IInterfaceSignInfo signInfo)
            {
                signInfo = null;
                if (!string.IsNullOrEmpty(timeStamp) && !string.IsNullOrEmpty(sign)&& !string.IsNullOrEmpty(partnerId))
                {
                    var cache = _interfaceSignProvider.GetInterfaceSignInfo(partnerId);//通过partnerId当key读取redis
                    if (cache.Enabled)
                    {
                        var areaName = filterContext.RouteData.DataTokens["area"]?.ToString().ToLower();//获取请求的area,即请求的是哪个插件
                        if (string.IsNullOrEmpty(areaName) || !cache.PluginCode.ToLower().StartsWith(areaName))
                        {
                            return false;//PluginCode需以areaName开头,否则意味着不是同一个插件(如:PluginCode=juwov1,areaName=JuWo)
                        }
                        if (DateTime.TryParseExact(timeStamp, "yyyyMMddHHmmss", CultureInfo.CurrentCulture.DateTimeFormat, DateTimeStyles.AllowWhiteSpaces, out var time) &&
                            (DateTime.Now - time).TotalMinutes <= 5)//时间戳有效期为5分钟
                        {
                            signInfo = cache;
                            var hashKey = EncryptHelper.Hash($"{timeStamp}:{body}:{partnerId}:{cache.PartnerKey}", "MD5").ToLowerInvariant();//MD5加密对比
                            return string.Equals(hashKey, sign);
                        }
                    }
                    
                }
                return false;
            }
    public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext){}
        }
    

    这样就实现了接口的签名验证了。但是还有一个问题是,如果同时存在多个不同的对接接口(插件)时,partnerId,PartnerKey应该是不一样的。即插件1和插件2的验证key是不能混用的。

    可以通过路由来区分不同的插件,来选择进入不同的area,通过area来区分不同的插件验证key。

        public class JuWoAreaRegistration: AreaRegistration
        {
            public override void RegisterArea(AreaRegistrationContext context)
            {
                context.MapRoute(
                    "JuWo_default",
                    "api/JuWo/{controller}/{action}/{id}",
                    new {action = "Index", id = UrlParameter.Optional},
                    new[] {"iERP.Its.Web.Areas.JuWo.Controllers"}
                );
            }
    
            public override string AreaName => "JuWo";
        }
    

     在之前的ValidSign方法中,通过var areaName = filterContext.RouteData.DataTokens["area"]?.ToString().ToLower();来获取到当前请求的是哪个插件,在把url上获取到的partnerId与我们之前约定好的比较看是否能对应。

  • 相关阅读:
    Java基础知识强化94:Calendar类之Calendar概述和获取日历字段的方法
    Java基础知识强化93:算一下你来到这个世界多少天的案例
    Java基础知识强化92:日期工具类的编写和测试案例
    Java基础知识强化91:DateFormat类之DateFormat实现日期和字符串的相互转换
    Java基础知识强化90:Date类之Data类中日期和毫秒相互转换
    Java基础知识强化89:Date类之Data类概述及其方法
    Java基础知识强化88:BigDecimal类之BigDecimal类引入和概述 以及 BigDecimal的使用(加减乘除)
    Java基础知识强化87:BigInteger类之BigInteger加减乘除法的使用
    Java基础知识强化86:BigInteger类之BigInteger概述和构造方法
    Java基础知识强化85:System类之arraycopy()方法(数组拷贝)
  • 原文地址:https://www.cnblogs.com/Cyril-hcj/p/13143657.html
Copyright © 2020-2023  润新知