.Net 使用log4.net进行日志脱敏的研究-.Net使用log4.net进行日志脱敏-.Net 日志脱敏
- 采用的方案
使用自定义的PatternLayout对记录的日志进行转换
- 步骤如下:
- 定义PatternLayout的消息转换器,使用正则匹配的方式对消息进行匹配和脱敏转换
- 定义PatternLayout使用自定义的消息转换器
- 对于匹配的需要脱敏的字段(正则表达式)进行配置管理,在应用启动阶段进行配置的加载
- 在log4.net配置文件的appender.layout节的 type定义使用我们自定义的PatternLayout,(原来是log4.net自带的默认的log4net.Layout.PatternLayout)
具体实现
- 定义PatternLayout的消息转换器
其中需要脱敏的字段的正则表达式是在应用启动阶段读取配置生成 (DESENSITIVE_REGEX)
上代码:
/// <summary>
/// 默认Log4Net的PatternLayout 的PatternConvert
/// </summary>
public class DefaultMessagePatternConvert : PatternLayoutConverter
{
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
if (loggingEvent.MessageObject is string)
{
//启用脱敏配置
if (Log4netDesensitiveManager.EnabledDesensitive)
{
string message = loggingEvent.MessageObject as string;
var result = Log4netDesensitiveManager.DESENSITIVE_REGEX.Replace(message, m => m.Groups[3].Success ? m.Groups[1].Value + m.Groups[2].Value + ReplaceDesensitiveMessage(m.Groups[1].Value, m.Groups[3].Value) : m.Groups[0].Value);
writer.Write(result);
return;
}
}
loggingEvent.WriteRenderedMessage(writer);
}
/// <summary>
/// 替换掩码
/// </summary>
/// <param name="fieldName"></param>
/// <param name="message"></param>
/// <returns></returns>
private string ReplaceDesensitiveMessage(string fieldName, string message)
{
var desensitiveConfig = Log4netDesensitiveManager.GetFieldDesensitiveConfig(fieldName);
if (desensitiveConfig.SaveAroundStartLen > 0)
{
if (desensitiveConfig.SaveAroundEndLen > 0)
{
return message.ReplaceDesensitiveAround(desensitiveConfig.SaveAroundStartLen, desensitiveConfig.SaveAroundEndLen);
}
else
{
return message.ReplaceDesensitiveAround(desensitiveConfig.SaveAroundStartLen);
}
}
else
{
return message.ReplaceDesensitive(desensitiveConfig.SaveMinLen, desensitiveConfig.SaveMaxLen);
}
}
}
- 定义PatternLayout使用自定义的消息转换器
继承log4.net的PatternLayout类,在构造函数中添加我们自定义的转换器
代码:
/// <summary>
/// 默认Log4Net的PatternLayout,替换log4net.Layout.PatternLayout
/// dou7.WebCore.Log.Log4net.DefaultLog4NetPatternLayout
/// </summary>
public class DefaultLog4NetPatternLayout: PatternLayout
{
public DefaultLog4NetPatternLayout():base()
{
//对message消息进行转换
this.AddConverter("message", typeof(DefaultMessagePatternConvert));
}
}
-
配置的管理和加载
-
代码:
/// <summary>
/// Log4net脱敏配置字段信息
/// </summary>
public class DesensitiveConfig
{
/// <summary>
/// 字段名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 脱敏最小保留长度,默认4,正数时,代表保留前面的位数,负数时,代表保留后面的位数
/// </summary>
public int SaveMinLen { get; set; }
/// <summary>
/// 脱敏最多要保留的长度
/// </summary>
public int SaveMaxLen { get; set; }
/// <summary>
/// 脱敏前面保留长度,默认4
/// </summary>
public int SaveAroundStartLen { get; set; }
/// <summary>
/// 脱敏后面保留长度,默认4
/// </summary>
public int SaveAroundEndLen { get; set; }
}
配置管理器:
/// <summary>
/// Log4net脱敏配置管理
/// 在启动时执行:Log4netDesensitiveManager.UseDesensitiveConfig(IConfiguration Configuration)
/// </summary>
public class Log4netDesensitiveManager
{
/// <summary>
/// 脱敏字段配置字典
/// </summary>
public static readonly Dictionary<string, DesensitiveConfig> ConfigMap = new Dictionary<string, DesensitiveConfig>();
/// <summary>
/// 脱敏字段配置KEY
/// </summary>
private static readonly string DESENSITIVE_CONF_KEY = "Log4net.Desensitive.Fields";
/// <summary>
/// 脱敏字段匹配Regex
/// </summary>
public static Regex DESENSITIVE_REGEX { get; private set; }
/// <summary>
/// 脱敏字段匹配表达式:"(Field1|Field2|FieldN...)("":""|':'|':|"":|:|\b)([^(""|'|,|{|\b)]*)"
/// </summary>
public static string PATTERN_STRING { get; private set; }
/// <summary>
/// 是否启用日志脱敏,默认不启用
/// </summary>
public static bool EnabledDesensitive { get; private set; }
/// <summary>
/// 使用脱敏配置,在启动时执行
/// </summary>
/// <param name="Configuration"></param>
public static void UseDesensitiveConfig(IConfiguration Configuration)
{
//初始化配置
InitConfig();
}
/// <summary>
/// 初始化配置
/// </summary>
private static void InitConfig()
{
Dictionary<string, string> Fields = new Dictionary<string, string>();
WebCoreApp.Configuration.GetSectionNode(DESENSITIVE_CONF_KEY).Bind(Fields);
ConfigMap.Clear();
foreach (var fieldName in Fields.Keys)
{
string fieldConfValue = Fields[fieldName];
DesensitiveConfig desensitiveConf = new DesensitiveConfig();
desensitiveConf.Name = fieldName;
string[] fieldConfs = fieldConfValue.Split(',');
if (fieldConfs.Length > 0)
{
desensitiveConf.SaveMinLen = fieldConfs[0].Trim().IntValue();
}
if (fieldConfs.Length > 1)
{
desensitiveConf.SaveMaxLen = fieldConfs[1].Trim().IntValue();
}
if (fieldConfs.Length > 2)
{
desensitiveConf.SaveAroundStartLen = fieldConfs[2].Trim().IntValue();
}
if (fieldConfs.Length > 3)
{
desensitiveConf.SaveAroundEndLen = fieldConfs[3].Trim().IntValue();
}
ConfigMap[fieldName] = desensitiveConf;
}
//初始化脱敏字段匹配表达式
InitPattern();
Logger.Info("初始化Log4net脱敏配置>>" + ConfigMap.ToJSON());
}
/// <summary>
/// 初始化脱敏字段匹配表达式
/// </summary>
private static void InitPattern()
{
if (ConfigMap.Keys == null || ConfigMap.Keys.Count == 0)
{
EnabledDesensitive = false;
return;
}
EnabledDesensitive = true;
string fields = ConfigMap.Keys.ToArray().Join("|");
PATTERN_STRING = "(" + fields + @")("":""|':'|':|"":|:|\b)([^(""|'|,|{|\b)]*)";
DESENSITIVE_REGEX = new Regex(PATTERN_STRING, RegexOptions.IgnoreCase | RegexOptions.Multiline);
}
/// <summary>
/// 获取脱敏字段掩码配置信息
/// </summary>
/// <param name="field"></param>
/// <returns></returns>
public static DesensitiveConfig GetFieldDesensitiveConfig(string field)
{
return ConfigMap[field];
}
}
替换字符串掩码的方法:
/// <summary>
/// 替换字符串掩码
/// </summary>
/// <param name="value">需要替换的字符串</param>
/// <param name="saveMinLen">最小保留长度,默认4,正数时,代表保留前面的位数,负数时,代表保留后面的位数</param>
/// <param name="saveMaxLen">最多要保留的长度</param>
/// <param name="specialChar">掩码字符,默认为*</param>
/// <returns>替换后的结果</returns>
public static string ReplaceDesensitive(this string value, int saveMinLen = 4, int saveMaxLen = -1, char specialChar = '*')
{
int saveLen = Math.Abs(saveMinLen);
if (value.Length < 2 || saveLen == 0)
{
return value;
}
if (saveLen > value.Length)
{
saveLen = value.Length - 1;
}
if (saveMaxLen > saveLen)
{
if (saveMaxLen < value.Length)
{
saveLen = saveMaxLen;
}
}
if (saveMinLen > 0)
{
string startStr = value.Substring(0, saveLen);
string specialStr = new string(specialChar, value.Length - saveLen);
return startStr + specialStr;
}
else
{
string endStr = value.Substring(value.Length - saveLen);
string specialStr = new string(specialChar, value.Length - saveLen);
return specialStr + endStr;
}
}
/// <summary>
/// 替换字符串前后掩码
/// </summary>
/// <param name="value">需要替换的字符串</param>
/// <param name="saveAroundLen">前后保留长度,默认4</param>
/// <param name="specialChar">特殊字符,默认为*</param>
/// <returns>替换后的结果</returns>
public static string ReplaceDesensitiveAround(this string value, int saveAroundLen = 4, char specialChar = '*')
{
if (value.Length < 2 || saveAroundLen == 0)
{
return value;
}
int half = value.Length / 2;
if (saveAroundLen > half)
{
saveAroundLen = half - 1;
}
int startLen = saveAroundLen;
int endLen = saveAroundLen;
if (saveAroundLen <= 0)
{
endLen = 1;
}
string startStr = value.Substring(0, startLen);
string endStr = value.Substring(value.Length - endLen);
string specialStr = new string(specialChar, value.Length - startLen - endLen);
return startStr + specialStr + endStr;
}
/// <summary>
/// 替换字符串前后掩码
/// </summary>
/// <param name="value">需要替换的字符串</param>
/// <param name="saveStartLen">前面保留长度</param>
/// <param name="saveEndLen">后面保留长度</param>
/// <param name="specialChar">特殊字符,默认为*</param>
/// <returns></returns>
public static string ReplaceDesensitiveAround(this string value, int saveStartLen, int saveEndLen, char specialChar = '*')
{
if (value.Length < 3 || saveStartLen + saveEndLen == 0)
{
return value;
}
if (saveStartLen + saveEndLen > value.Length)
{
int half = value.Length / 2;
saveStartLen = half - 1;
saveEndLen = half - 1;
if (half == 1)
{
if (saveStartLen > saveEndLen)
{
saveStartLen = 1;
}
else
{
saveEndLen = 1;
}
}
}
string startStr = value.Substring(0, saveStartLen);
string endStr = value.Substring(value.Length - saveEndLen);
string specialStr = new string(specialChar, value.Length - saveStartLen - saveEndLen);
return startStr + specialStr + endStr;
}
配置格式:
/**==================配置格式==================
* "Log4net": {
* "Desensitive": {
* "Fields": {
* "FIELD XXX": "FIELD XXX VALUE",
* }
* }
* }
* 其中FIELD XXX VALUE的格式为:SaveMinLen,SaveMaxLen,SaveAroundLen
* SaveMinLen:脱敏最小保留长度,默认4,正数时,代表保留前面的位数,负数时,代表保留后面的位数
* SaveMaxLen:脱敏最多要保留的长度
* SaveAroundStartLen:脱敏前面保留长度,默认4
* SaveAroundEndLen:脱敏后面保留长度
*示例:
* "Log4net": {
* "Desensitive": {
* "Fields": {
* //值格式:脱敏最小保留长度,脱敏最多要保留的长度,脱敏前面保留长度,脱敏后面保留长度
* "Name": "1,2,,",
* "IdNo": ",,4,",
* "Mobiler": ",,3,4",
* "Address": "10,15,,"
* }
* }
* }
*
*/
-
在应用启动阶段进行配置的加载示例
Log4netDesensitiveManager.UseDesensitiveConfig(IConfiguration Configuration); -
在log4.net配置文件的appender.layout节的 type定义
示例:
原来的:
…
…
…
使用自定义的PatternLayout配置文件如:
…
…
…
注意type=dou7.WebCore.Log.Log4net.DefaultLog4NetPatternLayout