在已有業務下面添加脫敏需求,不允許污染源方法,所以只能對返回值進行處理,這里列舉兩種方法,自定義序列化和過濾器。
自定義序列化應對所有用到該實體類的情況,過濾器應對只需要某一個或幾個請求脫敏的情況;
如果僅僅處理脫敏,直接使用通用脫敏工具類即可。
通用脫敏工具類代碼如下
1 public static class DesensitizationUtil 2 { 3 /// <summary> 4 /// 將傳入的字符串中間部分字符替換成特殊字符 5 /// </summary> 6 /// <param name="value">需要替換的字符串</param> 7 /// <param name="startLen">前保留長度,默認4</param> 8 /// <param name="subLen">要替換的長度,默認4</param> 9 /// <param name="specialChar">特殊字符,默認為*</param> 10 /// <returns>替換后的結果</returns> 11 public static string ReplaceWithSpecialChar(this string value, int startLen = 4, int subLen = 4, char specialChar = '*') 12 { 13 if (value.Length <= startLen + subLen) return value; 14 15 string startStr = value.Substring(0, startLen); 16 string endStr = value.Substring(startLen + subLen); 17 string specialStr = new string(specialChar, subLen); 18 19 return startStr + specialStr + endStr; 20 } 21 }
方式一:JsonConverter
JsonConverter的方式對實體類進行修飾,所有用到該實體類的地方都會按照指定的格式輸出。我們只需要輸出時脫敏,所以將CanRead設置為false,關閉反序列化,同時ReadJson不填寫邏輯代碼;CanWrite設置為true,WriteJson中將返回值value脫敏后寫入。
引用:
1 using Newtonsoft.Json;
具體代碼:
1 public class DesensitizationConvter : JsonConverter 2 { 3 //關閉反序列化 4 public override bool CanRead => false; 5 //開啟自定義序列化 6 public override bool CanWrite => true; 7 public override bool CanConvert(Type objectType) 8 { 9 return true; 10 } 11 private readonly int StartLen; 12 private readonly int SubLen; 13 private readonly char SpecialChar; 14 /// <summary> 15 /// 默認前保留長度4,脫敏長度4,特殊字符為* 16 /// </summary> 17 public DesensitizationConvter() 18 { 19 StartLen = 4; 20 SubLen = 4; 21 SpecialChar = '*'; 22 } 23 /// <summary> 24 /// 根據傳入的長度進行脫敏 25 /// </summary> 26 /// <param name="startLen">前保留長度</param> 27 /// <param name="subLen">脫敏長度</param> 28 public DesensitizationConvter(int startLen, int subLen) : this() 29 { 30 StartLen = startLen; 31 SubLen = subLen; 32 } 33 /// <summary> 34 /// 根據傳入的長度及特殊字符進行脫敏 35 /// </summary> 36 /// <param name="startLen">前保留長度</param> 37 /// <param name="subLen">脫敏長度</param> 38 /// <param name="specialChar">替換的特殊字符</param> 39 public DesensitizationConvter(int startLen, int subLen, char specialChar) : this(startLen, subLen) 40 { 41 SpecialChar = specialChar; 42 } 43 44 public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 45 { 46 throw new NotImplementedException(); 47 } 48 49 public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 50 { 51 if (value != null) 52 { 53 writer.WriteValue(value.ToString().ReplaceWithSpecialChar(StartLen, SubLen, SpecialChar)); 54 } 55 } 56 }
然后在實體類需要脫敏的屬性上面加上修飾:
1 [JsonConverter(typeof(DesensitizationConvter))] 2 public string Phone { get; set; } 3 4 [JsonConverter(typeof(DesensitizationConvter), 3, 4)] 5 public string Phone2 { get; set; } 6 7 [JsonConverter(typeof(DesensitizationConvter), 4, 10, '*')] 8 public string IDCard { get; set; }
WebApi請設置序列化工具類為Newtonsoft.Json,直接序列化方式為JsonConvert.SerializeObject(model)。
方式二:過濾器(JObject遍歷)
過濾器Filter的方式相比較於Json序列化更加靈活,哪個請求需要脫敏,就加上修飾,不影響其他請求結果。
引用:
1 using Newtonsoft.Json.Linq; 2 using System.Linq;
構造函數中為需要脫敏的對象賦值,這里為了更加靈活,使用的是動態傳參,然后處理成二維數組,支持多個屬性、自定義脫敏位置的傳參方式,包含需要脫敏的字段名稱、前保留長度、脫敏長度、脫敏的特殊符號。
具體代碼:
1 public class DesensitizationAttribute : Attribute, IActionFilter 2 { 3 public string[][] Desensitizations { get; set; } 4 // 動態傳參,可以多個字段脫敏 5 public DesensitizationAttribute(params string[] desensitizations) 6 { 7 Desensitizations = new string[desensitizations.Length][]; 8 for (int i = 0; i < desensitizations.Length; i++) 9 { 10 //處理中文逗號 11 desensitizations[i] = desensitizations[i].Replace(",", ","); 12 string[] str = desensitizations[i].Split(','); 13 //准備脫敏的參數 14 switch (str.Count()) 15 { 16 case 1: 17 Desensitizations[i] = new string[4] { str[0].Trim(), "4", "4", "*" }; 18 break; 19 case 3: 20 Desensitizations[i] = new string[4] { str[0].Trim(), str[1].Trim(), str[2].Trim(), "*" }; 21 break; 22 case 4: 23 Desensitizations[i] = new string[4] { str[0].Trim(), str[1].Trim(), str[2].Trim(), str[3].Trim() }; 24 break; 25 default: 26 break; 27 } 28 } 29 } 30 31 public void OnActionExecuting(ActionExecutingContext context) 32 { 33 34 } 35 36 public void OnActionExecuted(ActionExecutedContext context) 37 { 38 if (context.Result != null && context.Result is ObjectResult) 39 { 40 ObjectResult obj = context.Result as ObjectResult; 41 //轉換成JObject 42 JObject jo = JObject.FromObject(obj.Value); 43 //如果已知需要替換的字段層級關系,直接查詢脫敏即可,例如傳入A.B.Phone 44 //foreach (var item in Desensitizations) 45 //{ 46 // if (jo.SelectToken(item[0]) != null) 47 // { 48 // string newStr = jo.SelectToken(item[0]).ToString().ReplaceWithSpecialChar(Convert.ToInt32(item[1]), Convert.ToInt32(item[2]), Convert.ToChar(item[3])); 49 // jo.SelectToken(item[0]).Replace(newStr); 50 // } 51 //} 52 53 // 如果不知道層級,使用下面方法遍歷JObject 54 foreach (JToken item in jo.Values()){ 55 CommonReplaceConvter(item); 56 } 57 58 context.Result = new ObjectResult(jo); 59 } 60 } 61 62 /// <summary> 63 /// 遞歸遍歷JToken脫敏 64 /// </summary> 65 /// <param name="jt"></param> 66 public void CommonReplaceConvter(JToken jt) 67 { 68 if (jt == null) return; 69 70 if (jt.HasValues && jt.Values().Count() > 0) 71 { 72 foreach (JToken item in jt.Values()) 73 { 74 CommonReplaceConvter(item); 75 } 76 } 77 else 78 { 79 string[] str = Desensitizations.FirstOrDefault(t => t[0] == jt.Path.Split('.').Last()); 80 81 if (str != null) 82 { 83 string result = jt == null ? "" : jt.ToString().ReplaceWithSpecialChar(Convert.ToInt32(str[1]), Convert.ToInt32(str[2]), Convert.ToChar(str[3])); 84 jt.Replace(result); 85 } 86 } 87 } 88 }
最后在Controller上添加修飾,第一個參數表示Phone字段按照默認格式脫敏;第二個參數表示Phone2字段前面保留3位,脫敏長度為4,特殊字符為默認的*;第三個參數表示IDCard字段前面保留4位,脫敏長度為10,特殊字符為*(也可以改成自定義的字符):
[Desensitization("Phone", "Phone2,3,4", "IDCard,4,10,*")]
方式三:過濾器(反射)
引用:
1 using System.Collections; 2 using System.Linq; 3 using System.Reflection;
此方法與第二種方法類似,只是替換的方式不同,這里用的是反射。
構造函數中為需要脫敏的對象賦值,這里用的是二維數組,包含需要脫敏的字段名稱、前保留長度、脫敏長度、脫敏的特殊符號。具體傳值方法請往下看。
具體代碼:
1 public class DesensitizationAttribute : Attribute, IActionFilter 2 { 3 public string[][] Desensitizations { get; set; } 4 public DesensitizationAttribute(params string[] desensitizations) 5 { 6 Desensitizations = new string[desensitizations.Length][]; 7 for (int i = 0; i < desensitizations.Length; i++) 8 { 9 desensitizations[i] = desensitizations[i].Replace(",", ","); 10 string[] str = desensitizations[i].Split(','); 11 switch (str.Count()) 12 { 13 case 1: 14 Desensitizations[i] = new string[4] { str[0].Trim(), "4", "4", "*" }; 15 break; 16 case 3: 17 Desensitizations[i] = new string[4] { str[0].Trim(), str[1].Trim(), str[2].Trim(), "*" }; 18 break; 19 case 4: 20 Desensitizations[i] = new string[4] { str[0].Trim(), str[1].Trim(), str[2].Trim(), str[3].Trim() }; 21 break; 22 default: 23 break; 24 } 25 } 26 } 27 28 public void OnActionExecuting(ActionExecutingContext context) 29 { 30 31 } 32 33 public void OnActionExecuted(ActionExecutedContext context) 34 { 35 if (context.Result != null && context.Result is ObjectResult) 36 { 37 ObjectResult obj = context.Result as ObjectResult; 38 39 GetInfoPropertys(obj.Value); 40 context.Result = obj; 41 } 42 } 43 44 /// <summary> 45 /// 反射的方式遞歸脫敏 46 /// </summary> 47 /// <param name="obj">需要脫敏的對象</param> 48 public void GetInfoPropertys(object obj) 49 { 50 if (obj == null) return; 51 Type type = obj.GetType(); 52 if (type.IsGenericType) 53 { 54 //如果是List,需要遍歷 55 if (obj is ICollection Ilist) 56 { 57 foreach (object o in Ilist) 58 { 59 GetInfoPropertys(o); 60 } 61 } 62 return; 63 } 64 foreach (PropertyInfo property in type.GetProperties()) 65 { 66 object value = property.GetValue(obj, null); 67 if (property.PropertyType.IsValueType || property.PropertyType.Name.StartsWith("String")) 68 { 69 string[] str = Desensitizations.FirstOrDefault(t => t[0] == property.Name); 70 if (str != null) 71 { 72 string result = value == null ? "" : value.ToString().ReplaceWithSpecialChar(Convert.ToInt32(str[1]), Convert.ToInt32(str[2]), Convert.ToChar(str[3])); 73 property.SetValue(obj, result, null); 74 } 75 } 76 else 77 { 78 GetInfoPropertys(value); 79 } 80 } 81 } 82 }
同樣在Controller上添加修飾:
[Desensitization("Phone", "Phone2,3,4", "IDCard,4,10,*")]