Api接口簽名驗證


通過特性來統一驗證的入口,實現ActionFilterAttribute接口來進行接口的簽名驗證

 1 /// <summary>
 2     /// 標准接口基類Controller
 3     /// </summary>
 4     [SignVerification]
 5     public abstract class BaseApiController : Controller
 6     {
 7     }
 8     
 9     /// <summary>
10     /// 接口簽名驗證
11     /// </summary>
12     public class SignVerificationAttribute : ActionFilterAttribute,IAuthenticationFilter
13     {
14     }

 

實現的思路為:

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 參考簽名方式

具體代碼實現

 

 1 /// <summary>
 2     /// 接口簽名驗證
 3     /// </summary>
 4     public class SignVerificationAttribute : ActionFilterAttribute, IAuthenticationFilter
 5     {
 6         private readonly IDefaultUserService _defaultUserService;
 7         private readonly IInterfaceSignProvider _interfaceSignProvider;
 8         public SignVerificationAttribute()
 9         {
10             _defaultUserService = ObjectContainer.GetService<IDefaultUserService>();
11             _interfaceSignProvider = ObjectContainer.GetService<IInterfaceSignProvider>();
12         }
13 
14         public void OnAuthentication(AuthenticationContext filterContext)
15         {
16             var request = filterContext.HttpContext.Request;
17             var partnerId = request.QueryString["pid"];
18             var timeStamp = request.QueryString["ts"];
19             var sign = request.QueryString["sign"];//獲取Url參數
20             var body = GetBodyText(request.InputStream);
21 
22             if (!ValidSign(filterContext,timeStamp, sign, body,partnerId,out IInterfaceSignInfo signInfo))//加密驗證
23             {
24                 filterContext.Result = new ApiResult {Success = false, ErrorMessage = "無效簽名"};
25                 return;
26             }
27 
28             var service = ObjectContainer.GetService<IAuthenticationService>();
29             var userId = _defaultUserService.GetDefaultUserId(signInfo.LicNo);
30             var identity = service.SignIn(userId, signInfo.LicNo, false, TimeSpan.FromMinutes(5), SessionType.WebApi);
31             var newPrincipal = new GenericPrincipal(identity, new string[] { });
32             filterContext.Principal = newPrincipal;
33         }
34         private static string GetBodyText(Stream stream)
35         {
36             using (var ms = new MemoryStream())
37             {
38                 stream.CopyTo(ms);
39                 return Encoding.UTF8.GetString(ms.ToArray());
40             }
41         }
42 
43         private bool ValidSign(AuthenticationContext filterContext,string timeStamp, string sign, string body,string partnerId,out IInterfaceSignInfo signInfo)
44         {
45             signInfo = null;
46             if (!string.IsNullOrEmpty(timeStamp) && !string.IsNullOrEmpty(sign)&& !string.IsNullOrEmpty(partnerId))
47             {
48                 var cache = _interfaceSignProvider.GetInterfaceSignInfo(partnerId);//通過partnerId當key讀取redis
49                 if (cache.Enabled)
50                 {
51                     var areaName = filterContext.RouteData.DataTokens["area"]?.ToString().ToLower();//獲取請求的area,即請求的是哪個插件
52                     if (string.IsNullOrEmpty(areaName) || !cache.PluginCode.ToLower().StartsWith(areaName))
53                     {
54                         return false;//PluginCode需以areaName開頭,否則意味着不是同一個插件(如:PluginCode=juwov1,areaName=JuWo)
55                     }
56                     if (DateTime.TryParseExact(timeStamp, "yyyyMMddHHmmss", CultureInfo.CurrentCulture.DateTimeFormat, DateTimeStyles.AllowWhiteSpaces, out var time) &&
57                         (DateTime.Now - time).TotalMinutes <= 5)//時間戳有效期為5分鍾
58                     {
59                         signInfo = cache;
60                         var hashKey = EncryptHelper.Hash($"{timeStamp}:{body}:{partnerId}:{cache.PartnerKey}", "MD5").ToLowerInvariant();//MD5加密對比
61                         return string.Equals(hashKey, sign);
62                     }
63                 }
64                 
65             }
66             return false;
67         }
68 public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext){}
69     }

 

這樣就實現了接口的簽名驗證了。但是還有一個問題是,如果同時存在多個不同的對接接口(插件)時,partnerId,PartnerKey應該是不一樣的。即插件1和插件2的驗證key是不能混用的。

可以通過路由來區分不同的插件,來選擇進入不同的area,通過area來區分不同的插件驗證key。

 1 public class JuWoAreaRegistration: AreaRegistration
 2     {
 3         public override void RegisterArea(AreaRegistrationContext context)
 4         {
 5             context.MapRoute(
 6                 "JuWo_default",
 7                 "api/JuWo/{controller}/{action}/{id}",
 8                 new {action = "Index", id = UrlParameter.Optional},
 9                 new[] {"iERP.Its.Web.Areas.JuWo.Controllers"}
10             );
11         }
12 
13         public override string AreaName => "JuWo";
14     }

 

 在之前的ValidSign方法中,通過var areaName = filterContext.RouteData.DataTokens["area"]?.ToString().ToLower();來獲取到當前請求的是哪個插件,在把url上獲取到的partnerId與我們之前約定好的比較看是否能對應。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM