.NET Core 6敏感数据通用掩码方案:自定义[SensitiveField]属性实现
通用敏感字段掩码实现方案(.NET Core 6 API)
针对你需要在API层统一处理账号、SSN等敏感字段掩码的需求,以下是基于自定义属性的通用实现方案,替代原有重复的字段处理逻辑:
1. 定义自定义[SensitiveField]属性
创建标记类,用来标识需要掩码的字段,支持自定义保留位数和掩码字符:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class SensitiveFieldAttribute : Attribute { // 默认保留后4位 public int KeepLastCharacters { get; set; } = 4; // 默认用*作为掩码字符 public char MaskCharacter { get; set; } = '*'; }
2. 实现通用掩码处理工具类
编写复用性掩码逻辑,统一处理字符串的掩码规则:
public static class SensitiveDataMasker { public static string MaskSensitiveValue(string? value, int keepLast = 4, char maskChar = '*') { if (string.IsNullOrEmpty(value)) return value; // 长度小于等于保留位数时,直接返回原内容 if (value.Length <= keepLast) return value; var maskedPart = new string(maskChar, value.Length - keepLast); var visiblePart = value.Substring(value.Length - keepLast); return $"{maskedPart}{visiblePart}"; } }
3. 方案一:自动序列化时处理(推荐)
利用System.Text.Json的自定义转换器,在API返回JSON时自动根据权限掩码敏感字段,无需手动修改DTO内容:
3.1 自定义Json转换器
public class SensitiveFieldConverter : JsonConverter<string> { private readonly bool _shouldMask; // 传入权限判断结果,控制是否启用掩码 public SensitiveFieldConverter(bool shouldMask) { _shouldMask = shouldMask; } public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return reader.GetString(); } public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) { if (!_shouldMask) { writer.WriteStringValue(value); return; } // 获取当前字段的[SensitiveField]属性配置 var propertyInfo = options.Context.Context as PropertyInfo; var sensitiveAttr = propertyInfo?.GetCustomAttribute<SensitiveFieldAttribute>(); if (sensitiveAttr != null) { var maskedValue = SensitiveDataMasker.MaskSensitiveValue(value, sensitiveAttr.KeepLastCharacters, sensitiveAttr.MaskCharacter); writer.WriteStringValue(maskedValue); } else { writer.WriteStringValue(value); } } }
3.2 注册ActionFilter处理权限与转换器
public class SensitiveDataFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // 从HttpContext获取权限判断(示例:可根据Claims/业务逻辑调整) var noPermission = context.HttpContext.Items["NoPermission"] as bool? ?? false; // 配置序列化选项,注入自定义转换器 var jsonOptions = context.HttpContext.RequestServices.GetRequiredService<IOptions<JsonOptions>>().Value; jsonOptions.JsonSerializerOptions.Converters.Clear(); jsonOptions.JsonSerializerOptions.Converters.Add(new SensitiveFieldConverter(noPermission)); // 传递属性上下文,用于获取字段的[SensitiveField]配置 jsonOptions.JsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { typeInfo => { foreach (var property in typeInfo.Properties) { property.Context = new JsonSerializerContext { Context = property.PropertyInfo }; } } } }; } public void OnActionExecuted(ActionExecutedContext context) { } }
3.3 在Program.cs注册Filter
builder.Services.AddControllers(options => { options.Filters.Add<SensitiveDataFilter>(); });
4. 方案二:手动映射时处理(适用于非自动序列化场景)
如果需要手动控制DTO的掩码时机,可通过反射批量处理标记字段:
4.1 编写映射工具方法
public static class DtoMaskHelper { public static T MaskSensitiveFields<T>(T dto, bool noPermission) where T : class { if (!noPermission || dto == null) return dto; var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { var sensitiveAttr = prop.GetCustomAttribute<SensitiveFieldAttribute>(); if (sensitiveAttr != null && prop.PropertyType == typeof(string)) { var value = prop.GetValue(dto) as string; var maskedValue = SensitiveDataMasker.MaskSensitiveValue(value, sensitiveAttr.KeepLastCharacters, sensitiveAttr.MaskCharacter); prop.SetValue(dto, maskedValue); } } return dto; } }
4.2 替换原有业务代码
BankDetails form = new() { RoutingNumber = transmissionSettings?.RoutingNumber, AccountNumber = transmissionSettings?.AccountNumber, }; // 统一处理敏感字段掩码 if (NoPermission) { DtoMaskHelper.MaskSensitiveFields(form, NoPermission); }
5. DTO简化使用
现在你的DTO无需再写冗余的DisplayXXX属性,直接标记敏感字段即可:
public class BankDetails { // 默认规则:保留后4位,用*掩码 [SensitiveField] public string? RoutingNumber { get; set; } // 自定义掩码字符为# [SensitiveField(MaskCharacter = '#')] public string? AccountNumber { get; set; } }
内容的提问来源于stack exchange,提问作者sujith
相关产品推荐
相关产品推荐

