Apiインタフェース署名検証

5937 ワード

特性によって検証されたエントリを統一し、ActionFilterAttributeインタフェースを実現してインタフェースの署名検証を行う
    /// 
    ///       Controller
    /// 
    [SignVerification]
    public abstract class BaseApiController : Controller
    {
    }
    
    /// 
    ///       
    /// 
    public class SignVerificationAttribute : ActionFilterAttribute,IAuthenticationFilter
    {
    }

実現の構想は以下の通りである.
1.異なるドッキング先のインタフェース(プラグイン)は異なる検証keyを定義し、異なるプラグイン間で検証keyを混用できない
2.異なるプラグインは異なるpartnerId,partnerKeyを生成する.リクエストされたUrlにはパートナIdが必要であり、パートナIdをキーとしてredisで対応するプラグイン検証情報(パートナId、パートナKeyなど)を見つける
3.UrlパラメータにpartnerId,ts(タイムスタンプ)、sign(暗号化署名)を含める必要があります.tsタイムスタンプの有効時間は5分、signは(タイムスタンプ:formBody:partnerId:partnerKey)のMD 5暗号化
4.partnerIdで対応する検証情報を見つけてから(タイムスタンプ:formBody:partnerId:partnerKey)MD 5を暗号化しsignと比較して要求が改ざんされていないことを確認する
5.partnerIdが他のプラグインではなく現在のプラグインであることを確認します.redisは共通であり、keyによって値を取るだけです.
署名方式
タイムスタンプと要求Formパラメータ、およびPartnerKeyをコロンで接続します(タイムスタンプ:body:partnerId:PartnerKey)接続された文字列をMD 5によってsignを生成します
Urlパラメータ
パラメータ
説明
を選択します.
なければならない
コメント
pid
partnerId
string
はい
 
ts
タイムスタンプ(フォーマット:yyyyyMMddHmmss)
string
はい
タイムスタンプの有効時間は5分です
sign
MD 5(タイムスタンプ:body:partnerId:pkey)
string
はい
リファレンス署名方式
具体的なコード実装
    /// 
    ///       
    /// 
    public class SignVerificationAttribute : ActionFilterAttribute, IAuthenticationFilter
    {
        private readonly IDefaultUserService _defaultUserService;
        private readonly IInterfaceSignProvider _interfaceSignProvider;
        public SignVerificationAttribute()
        {
            _defaultUserService = ObjectContainer.GetService();
            _interfaceSignProvider = ObjectContainer.GetService();
        }

        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();
            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の検証キーは混用できない.
ルーティングによって異なるプラグインを区別し、異なる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を私たちが前に約束したものと比較して対応できるかどうかを確認します.