通過特性來統一驗證的入口,實現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與我們之前約定好的比較看是否能對應。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※帶您來了解什麼是 USB CONNECTOR ?
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?