Api接口签名验证

通过特征来统一验证的入口,实现ActionFilterAttribute接口来举行接口的署名验证

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

实现的思绪为:

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去取值而已

署名方式

记一次腾讯Android一面,拿走不谢!,【朝花夕拾】Android多线程之(二)ThreadLocal篇,android中getWidth()和getMeasuredWidth()之间的区别

将时间戳和请求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
    {
        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;
        }

    }

 

这样就实现了接口的署名验证了。然则另有一个问题是,若是同时存在多个差别的对接接口(插件)时,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与我们之前约定好的对照看是否能对应。

 

原创文章,作者:28x29新闻网,如若转载,请注明出处:https://www.28x29.com/archives/16274.html