欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

如何在 .Net 中优雅地脱敏输出字段

最编程 2024-05-24 14:59:43
...

背景:

在平常工作中,我们会遇到很多场景,需要对提供给前端(Web、APP)或三方的接口中敏感字段(如:姓名、身份证、手机号、银行卡等)进行脱敏处理。相信多数程序员同学最常用的方式是写一个脱敏的Util类:xxx.RealName = DesensitizationUtil.replaceRealName(xxx.RealName ); 相信这个大家都不陌生,但作为一个有追求的程序员咱必须得找一个比较“牛逼”看起来舒服点的写法。正好最近项目中有这样的需求,并且在网上.Net资料中这类文章很少,于是写了这个文章和大家分享。

 

思路:

鉴于之前在网上看过的一些资料,我决定通过在字段上加脱敏修饰类和自定义序列化处理,使用.Net特性Attribute结合Json.Net中的JsonConverter来实现。在需要脱敏的对象加自定义Converter修饰类:CommonReplaceConvter,在需要脱敏的字段上加自定义修改类:RegexReplaceAttribute。最后在序列化JsonConvert.SerializeObject方法执行时,会执行CommonReplaceConvter并获取字段中自定义属性RegexReplaceAttribute,如果存在则进行脱敏处理并重新赋值给当前字段。

 

实现:

1、定义RegexReplaceAttribute修饰类,包含2个属性(正则表达式,规则表达式)

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace JsonDemo.JsonNet
 8 {
 9     [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
10     public sealed class RegexReplaceAttribute : Attribute
11     {
12         public RegexReplaceAttribute(string regexp, string replacer)
13         {
14             this.Regexp = regexp;
15             this.Replacer = replacer;
16         }
17 
18         public string Regexp { get; set; }
19         public string Replacer { get; set; }
20     }
21 }

 

 

2、定义CommonReplaceConvter类,继承抽象类JsonConverter,重写CanConvert、ReadJson、WriteJson方法

 1 using Newtonsoft.Json;
 2 using Newtonsoft.Json.Linq;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Linq;
 6 using System.Reflection;
 7 using System.Runtime.CompilerServices;
 8 using System.Text;
 9 using System.Text.RegularExpressions;
10 using System.Threading.Tasks;
11 
12 namespace JsonDemo.JsonNet
13 {
14     public class CommonReplaceConvter : JsonConverter
15     {
16         public override bool CanConvert(Type objectType)
17         {
18             return true;
19         }
20 
21         public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
22         {
23             //获取父对象类型
24             object rootObject = Activator.CreateInstance(objectType);
25             //从JsonReader中获取当前对象的值
26             JToken data = JToken.ReadFrom(reader);
27             object propValue;
28             //对属性进行遍历
29             foreach (var token in data)
30             {
31                 //获取属性信息
32                 PropertyInfo propInfo = rootObject.GetType().GetProperty
33                     (token.Path, BindingFlags.IgnoreCase | BindingFlags.Public |
34                      BindingFlags.Instance);
35                 //当前字段可写
36                 if (propInfo.CanWrite)
37                 {
38                     var tk = token as JProperty;
39                     propValue = tk.Value;
40                     if (tk.Value is JObject)
41                     {
42                         JValue val = tk.Value.SelectToken("value") as JValue;
43                         propValue = val.Value;
44                     }
45 
46                     //获取正则替换自定义修饰类
47                     var regexReplaceAttr = propInfo.GetCustomAttribute<RegexReplaceAttribute>();
48                     //当前字段存在修饰类,进行正则替换脱敏
49                     if (regexReplaceAttr != null && propValue != null)
50                     {
51                         propValue = Regex.Replace(propValue.ToString(), regexReplaceAttr.Regexp, regexReplaceAttr.Replacer);
52                     }
53 
54                     propInfo.SetValue(rootObject, Convert.ChangeType(propValue, propInfo.PropertyType.UnderlyingSystemType), null);
55                 }
56             }
57             return rootObject;
58         }
59 
60         public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
61         {
62             var jo = new JObject();
63             var type = value.GetType();
64             foreach (PropertyInfo propInfo in type.GetProperties())
65             {
66                 if (propInfo.CanRead)
67                 {
68                     object propVal = propInfo.GetValue(value, null);
69                     jo.Add(propInfo.Name, JToken.FromObject(propVal ?? string.Empty, serializer));
70                 }
71             }
72             jo.WriteTo(writer);
73         }
74     }
75 }

 

3、在需要脱敏的类上增加修饰特性[JsonConverter(typeof(CommonReplaceConvter))],脱敏的字段上增加修饰特性[RegexReplace(@"(\d{3})(\d{4})(\d{4})", "$1****$3")]

 1 using JsonDemo.JsonNet;
 2 using Newtonsoft.Json;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Linq;
 6 using System.Text;
 7 using System.Threading.Tasks;
 8 
 9 namespace JsonDemo.Models
10 {
11     [JsonConverter(typeof(CommonReplaceConvter))]
12     public class UserInfo2
13     {
14         public int UserId { get; set; }
15 
16         [RegexReplace(@"(\d{3})(\d{4})(\d{4})", "$1****$3")]
17         public string Phone { get; set; }
18 
19         [RegexReplace(@"(\d{4})(\d{10})([\dxX]{4})", "$1**********$3")]
20         public string IdNumber { get; set; }
21 
22         [RegexReplace(@"([\u4e00-\u9fa5])([\u4e00-\u9fa5]+)", "*$2")]
23         public string RealName { get; set; }
24     }
25 }

 

示例:

 1 using JsonDemo.Models;
 2 using Newtonsoft.Json;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Diagnostics;
 6 using System.Linq;
 7 using System.Text;
 8 using System.Threading.Tasks;
 9 
10 namespace JsonDemo
11 {
12     class Program
13     {
14         static void Main(string[] args)
15         {
16             var json = "{\"UserId\":123,\"Phone\":\"13512345678\",\"IdNumber\":\"11010119900307061X\",\"RealName\":\"张三\"}";
17             var user1 = JsonConvert.DeserializeObject<UserInfo>(json);
18             var st = new Stopwatch();
19             st.Start();
20             Console.WriteLine("user1:" + JsonConvert.SerializeObject(user1));
21             st.Stop();
22             Console.WriteLine("user1耗时:" + st.ElapsedMilliseconds + "\n\n");
23 
24             var user2 = JsonConvert.DeserializeObject<UserInfo2>(json);
25             st.Restart();
26             Console.WriteLine("user2:" + JsonConvert.SerializeObject(user2));
27             st.Stop();
28             Console.WriteLine("user2耗时:" + st.ElapsedMilliseconds + "\n\n");
29 
30             Console.ReadLine();
31         }
32     }
33 }

 

 

总结:

以上为此方案实现的全过程,怎么样是不是比某某某Util方法要优雅很多。扩展性是不是也更好,有其他特殊脱敏逻辑的时候,可以继续自定义Attribute特性,并在Converter中获取特性进行业务逻辑处理,适用场景也比较多(日志脱敏打印、根据权限脱敏等)

 

说明:

软件环境(.NetFramework 4.6 + Newtonsoft.Json 6.0.8)

由于此处为演示Demo,真实项目中如Asp.Net Web Api 或 .Net Core Web Api中需要将Json序列化器指定为Json.Net,否则脱敏会不生效

以上代码有些反射的地方未加缓存优化,此处仅提供一种思路,实际场景中对性能有要求的可自行考虑优化

推荐阅读