You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 5.0 API如何实现带敏感数据脱敏的请求响应日志记录

.NET 5 API全局请求响应日志+敏感字段脱敏实现方案

核心特性读取方法

你需要的读取属性上自定义[SensitiveData]特性的逻辑,可通过反射实现,两种常用写法:

  • 直接判断是否标记特性:Attribute.IsDefined(属性实例, typeof(SensitiveDataAttribute))
  • 读取特性实例(适合带参数的特性):属性实例.GetCustomAttribute<SensitiveDataAttribute>() != null

完整实现步骤

1. 实现脱敏工具类

该类负责遍历对象属性,对标记了敏感特性的字段做替换处理:

using System.Reflection;
using System.Text.Json;

public static class SensitiveDataMasker
{
    // 自定义脱敏替换符
    private const string MaskString = "***";

    public static string MaskObject(object obj)
    {
        if (obj == null) return string.Empty;
        var type = obj.GetType();
        var result = new Dictionary<string, object>();
        // 遍历所有公共实例属性
        var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var prop in properties)
        {
            // 检查是否为敏感字段
            if (Attribute.IsDefined(prop, typeof(SensitiveDataAttribute)))
            {
                result[prop.Name] = MaskString;
                continue;
            }
            var value = prop.GetValue(obj);
            // 嵌套复杂类型可加递归逻辑处理,这里简单处理直接赋值
            result[prop.Name] = value;
        }
        // 序列化脱敏后的对象返回
        return JsonSerializer.Serialize(result);
    }
}

2. 实现全局日志中间件

中间件可以全局捕获所有端点的请求和响应,注意要开启请求响应流的倒带,避免读取流后接口无法正常获取数据:

using Microsoft.AspNetCore.Http;
using Serilog;
using System.IO;
using System.Threading.Tasks;

public class RequestResponseLogMiddleware
{
    private readonly RequestDelegate _next;

    public RequestResponseLogMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 处理请求日志
        context.Request.EnableBuffering();
        var requestReader = new StreamReader(context.Request.Body);
        var requestContent = await requestReader.ReadToEndAsync();
        context.Request.Body.Position = 0;

        string maskedRequest = requestContent;
        // 仅处理JSON类型的请求
        if (!string.IsNullOrWhiteSpace(requestContent) 
            && context.Request.ContentType?.Contains("application/json") == true)
        {
            try
            {
                var requestObj = JsonSerializer.Deserialize<object>(requestContent);
                maskedRequest = SensitiveDataMasker.MaskObject(requestObj);
            }
            catch
            {
                // 反序列化失败直接保留原始内容
            }
        }

        Log.Information("请求日志: 路径={Path}, 方法={Method}, 内容={Content}",
            context.Request.Path, context.Request.Method, maskedRequest);

        // 处理响应日志
        var originalResponseBody = context.Response.Body;
        using var responseStream = new MemoryStream();
        context.Response.Body = responseStream;

        await _next(context);

        responseStream.Seek(0, SeekOrigin.Begin);
        var responseContent = await new StreamReader(responseStream).ReadToEndAsync();
        responseStream.Seek(0, SeekOrigin.Begin);

        string maskedResponse = responseContent;
        if (!string.IsNullOrWhiteSpace(responseContent)
            && context.Response.ContentType?.Contains("application/json") == true)
        {
            try
            {
                var responseObj = JsonSerializer.Deserialize<object>(responseContent);
                maskedResponse = SensitiveDataMasker.MaskObject(responseObj);
            }
            catch
            {
                // 反序列化失败直接保留原始内容
            }
        }

        Log.Information("响应日志: 路径={Path}, 状态码={StatusCode}, 内容={Content}",
            context.Request.Path, context.Response.StatusCode, maskedResponse);

        await responseStream.CopyToAsync(originalResponseBody);
    }
}

3. 注册服务

在Startup.cs的Configure方法中注册中间件,注意要放在路由匹配之前:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 放在最开头,或者Serilog注册之后
    app.UseMiddleware<RequestResponseLogMiddleware>();

    app.UseRouting();
    // 其他中间件...
    app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}

测试效果

当接口返回你定义的Document对象时,日志输出中NationalId字段会自动脱敏:

响应日志: 路径=/api/document/123, 状态码=200, 内容={"Id":123,"NationalId":"***","Data":""}

优化提示

  • 如果有嵌套对象的敏感字段,在SensitiveDataMasker中增加递归处理逻辑即可
  • 文件上传、form-data等非JSON请求可以增加跳过逻辑,避免影响正常业务
  • 高并发场景可以把属性的特性检查结果做缓存,减少重复反射的性能开销

内容的提问来源于stack exchange,提问作者olamatre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 15:51:02