前言:第一次写文章,有问题请轻喷
当前使用 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