前言:第一次寫文章,有問題請輕噴
當前使用 Net Core 版本 2.1.3
我們經常在開發中需要把實體的主鍵 Id 傳輸到前端,但是在Get的時候又不想讓前端能看到明文,我們通常會加密這些數據,所以有了這篇文章來寫一些心得。(主要是我在網上找的代碼寫得太簡單了,不符合我的需求)
這里我用的是 Net Core 自帶的 DataProtector ,使用方式自行百度一下
關於中間件 Middleware 可以看看博園大佬寫的,太多就不列舉了,官方文檔:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/index?view=aspnetcore-2.2
一張圖來概括就是這樣:
1. 中間件完整代碼
public class DataProtectMiddleware { private readonly RequestDelegate _next; private readonly IDataProtector _dataProtector; public DataProtectMiddleware(RequestDelegate requestDelegate, IDataProtectionProvider dataProtection) { _dataProtector = dataProtection.CreateProtector("defaultProtector"); _next = requestDelegate; } private static readonly string _matchJsonIdExpression = "\"[a-zA-Z0-9]+id\""; private static readonly string _matchJsonIdValueExpression = "\"[a-zA-Z0-9_\\-]+\""; private static readonly string _matchQueryIdExpression = "[a-zA-Z0-9]+id"; private static readonly string _matchQueryIdValueExpression = "[a-zA-Z0-9_\\-]+"; private Regex _matchJsonIdKeyValue = new Regex($"{_matchJsonIdExpression}:{_matchJsonIdValueExpression}", RegexOptions.IgnoreCase); private Regex _matchQueryIdKeyValue = new Regex($"{_matchQueryIdExpression}={_matchQueryIdValueExpression}", RegexOptions.IgnoreCase); public async Task Invoke(HttpContext context) { // 替換原本的 Response.Body 流在 _next(context) 執行下一個中間件后,需要讀取數據,原本的流不可讀 canReader = false var originalResponseStream = context.Response.Body; using var replaceResponseStream = new MemoryStream(); context.Response.Body = replaceResponseStream; // 過濾請求 await FilterRequest(context); await _next(context); if (context.Response.StatusCode == StatusCodes.Status204NoContent) return; // 過濾響應 await FilterResponse(context, originalResponseStream, replaceResponseStream); } private async Task FilterResponse(HttpContext context, Stream originalResponseStream, MemoryStream replaceResponseStream) { var responseData = new StringBuilder(); using (var reader = new StreamReader(replaceResponseStream)) { context.Response.Body.Seek(0, SeekOrigin.Begin); responseData = new StringBuilder(await reader.ReadToEndAsync()); // 篩選以Id結尾的字段,並將ID加密 var matchedIdCollection = _matchJsonIdKeyValue.Matches(responseData.ToString()); foreach (Match itemMathId in matchedIdCollection) { var unprotectId = Regex.Match(itemMathId.Value, $"{_matchJsonIdValueExpression}$").Value.Replace("\"", ""); var protectId = Regex.Replace(itemMathId.Value, $"{_matchJsonIdValueExpression}$", $"\"{_dataProtector.Protect(unprotectId)}\""); responseData = responseData.Replace(itemMathId.Value, protectId); } } // 將返回的 Response 流 Copy 到原始流 await originalResponseStream.WriteAsync(Encoding.Default.GetBytes(responseData.ToString())); context.Response.Body = originalResponseStream; } private async Task<HttpContext> FilterRequest(HttpContext context) { // 可以考慮反序列化為對象,更加靈活控制加密字段,這里使用正則因為 簡單,粗暴,快 反射要慢一點 var requestData = new StringBuilder(); // 過濾 Get 請求中 QueryString 的 Id 值,並解密 if (context.Request.Method.Equals(HttpMethods.Get, StringComparison.CurrentCultureIgnoreCase)) { requestData.Append(context.Request.QueryString); var matchedIdCollection = _matchQueryIdKeyValue.Matches(requestData.ToString()); foreach (Match itemMathId in matchedIdCollection) { var protectId = Regex.Match(itemMathId.Value, $"{_matchQueryIdValueExpression}$").Value; var unprotectId = Regex.Replace(itemMathId.Value, $"{_matchQueryIdValueExpression}$", $"{_dataProtector.Unprotect(protectId)}"); requestData = requestData.Replace(itemMathId.Value, unprotectId); } context.Request.QueryString = new QueryString(requestData.ToString()); } if (context.Request.Method.Equals(HttpMethods.Post, StringComparison.CurrentCultureIgnoreCase)) { // 過濾 Post 請求 Body Stream 中的 Id 值,並解密 using (var reader = new StreamReader(context.Request.Body)) { requestData.Append(await reader.ReadToEndAsync()); var matchedIdCollection = _matchJsonIdKeyValue.Matches(requestData.ToString()); foreach (Match itemMathId in matchedIdCollection) { var protectId = Regex.Match(itemMathId.Value, $"{_matchJsonIdValueExpression}$").Value.Replace("\"", ""); var unProtectId = Regex.Replace(itemMathId.Value, $"{_matchJsonIdValueExpression}$", $"\"{_dataProtector.Unprotect(protectId)}\""); requestData = requestData.Replace(itemMathId.Value, unProtectId); } } var requestStringContent = new StringContent(requestData.ToString()); context.Request.Body = await requestStringContent.ReadAsStreamAsync(); } return context; } }
整體執行流程圖
寫在最后:整個項目就不發上去了,幫朋友寫的一個小玩意,這個類文件我發布到百度網盤把。
鏈接: https://pan.baidu.com/s/1m72tHkw8zAzYYpWO0Yw2FQ 提取碼: r3qh