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 

 


免責聲明!

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



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