Net Core 自定义 Middleware 加密解密(更新)

前言:第一次写文章,有问题请轻喷

当前使用 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 

 

posted @ 2019-09-16 10:59  一直踩坑的码农  阅读(1939)  评论(0编辑  收藏  举报