如何在JSON中屏蔽敏感值以用于日志记录



我有几个类似的JSON结构,我想将它们写入SQL表中以用于日志记录。但是,JSON中的一些字段包含敏感信息,我希望部分屏蔽这些信息,以便在日志中看不到完整的值。

下面是一个JSON结构示例:

{
"Vault": 1,
"Transaction": {
"gateway": {
"Login": "Nick",
"Password": "Password"
},
"credit_card": {
"number": "4111111111111"
}
}
}

在这种情况下,我试图更改4111信用卡号,使其在JSON中看起来像4xxx1111。我正在使用Newtonsoft,并已将JSON反序列化为JObject,但我一直在思考如何屏蔽该值。我认为线索与JToken有关,但还没有弄清楚。我想让这个解决方案尽可能通用,这样它就可以与我可能需要注销的任何JSON结构一起使用。

如有任何帮助,我们将不胜感激。

以下是我认为应该采取的方法:

  1. 制作一个助手方法,该方法可以采用字符串值,并按照日志所需的方式对其进行模糊处理。也许是这样的东西,例如:

    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);
    }
    
  2. 制作另一个可以接受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())));
    }
    }
    }
    
  3. 最后,编译一个JSONPath表达式列表,用于您希望在期望获得的所有JSON体中隐藏的值。从上面的示例JSON中,我认为您可能希望在Password出现的任何位置对其进行模糊处理,如果number出现在credit_card内部,则对其进行隐藏处理。用JSONPath表示,它们将分别是$..Password$..credit_card.number。(请记住,Json.Net中的JSONPath表达式区分大小写。)将此列表放入某个配置设置中,以便在需要时轻松更改。

  4. 现在,每当您想注销某些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);

相关内容

  • 没有找到相关文章

最新更新