以logback日志为例,log4j日志原理一致
原理
继承MessageConverter类,重写convert方法。获取到输出的日志信息,对日志信息使用正则匹配规则,获取到key-value键值对。根据指定的key键,修改其value值,使其日志信息关键词脱敏。
代码实现
是否开启脱敏字段、脱敏关键词字段可使用yml配置文件配置,使其更加灵活。
将重写好的日志脱敏类,放在logback.xml文件中即可,和<appender>标签是同级别存在的。
控制台以及日志文件输出就是脱敏的信息,保证的数据的安全性
<!--自定义脱敏类-->
<conversionRule conversionWord="msg" converterClass="org.example.config.SensitiveConverterFilter"/>
import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 日志脱敏过滤器
*
* @author 苦瓜不苦
* @date 2022/11/2 10:59
**/
public class SensitiveConverterFilter extends MessageConverter {
// 正则匹配模式 - 该正则表达式第三个()可能无法匹配以某些特殊符号开头和结尾的(如果像密码这种字段,前后如果有很多特殊字段,则无法匹配)
public static final Pattern REGEX_PATTERN = Pattern.compile("\\s*([\"]?[\\w]+[\"]?)(\\s*[:=]+[^\\u4e00-\\u9fa5@,.*{\\[\\w]*\\s*)([\\u4e00-\\u9fa5_\\-@.\\w]+)[\\W&&[^\\-@.]]?\\s*");
// 是否开启脱敏
private static final Boolean ENABLE = true;
// 脱敏的关键词KEY
private static final List<String> DESENSITIZATION_KEY_LIST = Arrays.asList("age", "name");
@Override
public String convert(ILoggingEvent event) {
// 获取原始日志
String message = event.getFormattedMessage();
// 返回脱敏后的日志
return filterSensitive(message);
}
/**
* 敏感信息脱敏
*
* @param message 日志信息
* @return
*/
private String filterSensitive(String message) {
// 判断是否开启
if (!ENABLE) {
return message;
}
// 判断是否填写关键词
if (DESENSITIZATION_KEY_LIST.isEmpty()) {
return message;
}
// 判断日志信息是否为空
if (Objects.isNull(message) || message.isEmpty()) {
return message;
}
// 正则匹配脱敏集合
Map<String, String> map = matchMessage(DESENSITIZATION_KEY_LIST, message);
// 替换脱敏的字符串
return replaceMessage(map, message);
}
/**
* 正则匹配脱敏集合
*
* @param keyList 脱敏的key集合
* @param message 日志信息
* @return
*/
private static Map<String, String> matchMessage(List<String> keyList, String message) {
Map<String, String> map = new HashMap<>();
if (keyList == null || keyList.isEmpty()) {
return map;
}
// 正则匹配
Matcher matcher = REGEX_PATTERN.matcher(message);
while (matcher.find()) {
// 获取key值
String key = matcher.group(1);
// 修正key
if (key.contains("\"")) {
key = key.replace("\"", "");
}
// 判断是否需要脱敏
if (keyList.contains(key)) {
// 获取键值对
String pair = matcher.group();
// 获取value值
String value = matcher.group(3);
// value值脱敏
String valueUpdate = desensitization(value);
// 替换脱敏后的键值对
String pairUpdate = pair.replace(value, valueUpdate);
// 储存Map集合
map.put(pair, pairUpdate);
}
}
return map;
}
/**
* 替换脱敏的字符串
*
* @param map 需要脱敏的集合
* @param message 日志信息
* @return
*/
private static String replaceMessage(Map<String, String> map, String message) {
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
message = message.replace(key, value);
}
return message;
}
/**
* 脱敏
*
* @param message 日志信息
* @return
*/
private static String desensitization(String message) {
if (Objects.isNull(message) || message.isEmpty()) {
return message;
}
int end = message.length();
int start = end < 2 ? 0 : 1;
end = end > 2 ? end - 1 : end;
String substring = message.substring(start, end);
StringBuilder bit = new StringBuilder();
for (int i = 0; i < substring.length(); i++) {
bit.append("*");
}
return message.replace(substring, bit.toString());
}
}