我有几个类似的JSON结构,我想将它们写入SQL表中以用于日志记录。但是,JSON中的一些字段包含敏感信息,我希望部分屏蔽这些信息,以便在日志中看不到完整的值。
下面是一个JSON结构示例:
{
"Vault": 1,
"Transaction": {
"gateway": {
"Login": "Nick",
"Password": "Password"
},
"credit_card": {
"number": "4111111111111"
}
}
}
在这种情况下,我试图更改4111
信用卡号,使其在JSON中看起来像4xxx1111
。我正在使用Newtonsoft,并已将JSON反序列化为JObject
,但我一直在思考如何屏蔽该值。我认为线索与JToken
有关,但还没有弄清楚。我想让这个解决方案尽可能通用,这样它就可以与我可能需要注销的任何JSON结构一起使用。
如有任何帮助,我们将不胜感激。
以下是我认为应该采取的方法:
-
制作一个助手方法,该方法可以采用字符串值,并按照日志所需的方式对其进行模糊处理。也许是这样的东西,例如:
public static string Obscure(string s) { if (string.IsNullOrEmpty(s)) return s; int len = s.Length; int leftLen = len > 4 ? 1 : 0; int rightLen = len > 6 ? Math.Min((len - 6) / 2, 4) : 0; return s.Substring(0, leftLen) + new string('*', len - leftLen - rightLen) + s.Substring(len - rightLen); }
-
制作另一个可以接受
JToken
和JSONPath表达式列表的辅助方法。在此方法中,使用SelectTokens
将每条路径与令牌的内容进行匹配。对于找到的每个匹配,使用第一个辅助方法将敏感值替换为模糊版本。public static void ObscureMatchingValues(JToken token, IEnumerable<string> jsonPaths) { foreach (string path in jsonPaths) { foreach (JToken match in token.SelectTokens(path)) { match.Replace(new JValue(Obscure(match.ToString()))); } } }
-
最后,编译一个JSONPath表达式列表,用于您希望在期望获得的所有JSON体中隐藏的值。从上面的示例JSON中,我认为您可能希望在
Password
出现的任何位置对其进行模糊处理,如果number
出现在credit_card
内部,则对其进行隐藏处理。用JSONPath表示,它们将分别是$..Password
和$..credit_card.number
。(请记住,Json.Net中的JSONPath表达式区分大小写。)将此列表放入某个配置设置中,以便在需要时轻松更改。 -
现在,每当您想注销某些JSON时,只需执行以下操作:
JToken token = JToken.Parse(json); string[] jsonPaths = YourConfigSettings.GetJsonPathsToObscure(); ObscureMatchingValues(token, jsonPaths); YourLogger.Log(token.ToString(Formatting.None));
演示小提琴:https://dotnetfiddle.net/dGPyJF
您可以使用Json Converter来转换特定的命名属性以进行屏蔽。这里有一个例子:
public class KeysJsonConverter : JsonConverter
{
private readonly Type[] _types;
private readonly string[] _pinValues= new[] { "number","Password" };
public KeysJsonConverter(params Type[] types)
{
_types = types;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
IList<JProperty> propertyNames = o.Properties().Where(p => _pinValues.Contains(p.Name)).ToList();
foreach (var property in propertyNames)
{
string propertyValue = (string)property.Value;
property.Value = propertyValue?.Length > 2 ? propertyValue.Substring(0, 2).PadRight(propertyValue.Length, '*') : "Invalid Value";
}
o.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
}
然后ANd将Json称为:JsonConvert.SerializeObject(Result, new KeysJsonConverter(typeof(Method)))
您可以使用反射来实现这一点,但首先,创建一个属性并标记要隐藏的属性:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SensitiveDataAttribute: Attribute{}
public class User
{
public string Username { get; set; }
[SensitiveData]
public string Password { get; set; }
}
public static class Obfuscator
{
private const string Masked = "***";
public static T MaskSensitiveData <T> (T value)
{
return Recursion(value, typeof(T));
}
# region Recursive reflection
private static object Recursion(object inputObj, Type type)
{
try {
if (inputObj != null)
{
if (type.IsArray)
{
//Input object is an array
//Iterate array elements
IterateArrayElements(ref inputObj);
}
else
{
//Input object is not an array
//Iterate properties
IteratePropertiesAndFields(ref inputObj);
}
return inputObj;
}
}
catch
{
//Die quietly :'(
}
return null;
}
private static void IterateArrayElements(ref object inputObj)
{
var elementType = inputObj ? .GetType().GetElementType();
var elements = (IEnumerable)inputObj;
foreach(var element in elements)
{
Recursion(element, elementType);
}
}
private static void IteratePropertiesAndFields(ref object inputObj)
{
var type = inputObj ? .GetType();
if (type == null)
return;
if (type.IsArray)
{
//is an array
IterateArrayElements(ref inputObj);
}
else
{
foreach(var property in type.GetProperties().Where(x => x.PropertyType.IsPublic))
{
if (Attribute.IsDefined(property, typeof(SensitiveDataAttribute)))
{
if (property.PropertyType == typeof(string) || type == typeof(string))
{
//we can mask only string
property.SetValue(inputObj, Masked);
}
else
{
//all properties that are not string set to null
property.SetValue(inputObj, null);
}
}
else if (property.PropertyType.IsArray)
{
//Property is an array
Recursion(property.GetValue(inputObj), property.PropertyType);
}
}
foreach(var property in type.GetRuntimeFields().Where(x => x.FieldType.IsPublic))
{
if (Attribute.IsDefined(property, typeof(SensitiveDataAttribute)))
{
if (property.FieldType == typeof(string) || type == typeof(string))
{
//we can mask only string
property.SetValue(inputObj, Masked);
}
else
{
//all Fields that are not string set to null
property.SetValue(inputObj, null);
}
}
else if (property.FieldType.IsArray)
{
//Field is an array
Recursion(property.GetValue(inputObj), property.FieldType);
}
}
}
}
# endregion
}
然后称之为
var user = new User
{
Username = "Joe",
Password = "12345"
}
var myobj = Obfuscator.MaskSensitiveData<User>(user);