日志脱敏首先要搞清楚,影响的数据范围,是要全局支持日志脱敏,还是只针对部分代码。
如果涉及到敏感数据的业务代码较少,建议写个数据脱敏工具类,在打印日志的时候调用,灵活可靠,影响范围小。
一、第一种方案:全局方式
针对log4j2的日志脱敏实现方案,重写rewrite方法,对敏感数据进行脱敏操作
需要在 引入此插件,插件名称为DataMaskingRewritePolicy。
1、重写rewrite方法
import ;
import ;
import ;
import ;
import .;
import .;
import .;
import .;
import .;
import ..Log4jLogEvent;
import .;
import .;
import .;
import ;
import ;
import ;
import ;
import ;
/**
*
* 针对log4j2的日志脱敏实现方案,重写rewrite方法,调整日志内容
*
* 需要在 引入此插件,插件名称为DataMaskingRewritePolicy
*
*/
@Plugin(name = "DataMaskingRewritePolicy", category = "Core", elementType = "rewritePolicy", printObject = true)
public class DataMaskingRewritePolicy implements RewritePolicy {
/*
* 脱敏符号
*/
private static final String ASTERISK = "****";
/*
* 引号
*/
private static final String QUOTATION_MARK = "\"";
// 使用静态内部类创建对象,节省空间
private static class StaticDataMaskingRewritePolicy {
private static final DataMaskingRewritePolicy dataMaskingRewritePolicy = new DataMaskingRewritePolicy();
}
// 需要加密的字段配置数组
private static final String[] encryptionKeyArrays = { "password", "expireYear", "expireMonth",
"cvv" };
// 将数组转换为集合,方便查找
private static final List<String> encryptionKeys = new ArrayList<>();
public DataMaskingRewritePolicy() {
if ((encryptionKeys)) {
((encryptionKeyArrays));
}
}
/**
* 日志修改方法,可以对日志进行过滤,修改
*
* @param logEvent
* @return
*/
@Override
public LogEvent rewrite(LogEvent logEvent) {
if (!(logEvent instanceof Log4jLogEvent)) {
return logEvent;
}
Log4jLogEvent log4jLogEvent = (Log4jLogEvent) logEvent;
Message message = ();
if ((message instanceof ParameterizedMessage)) {
ParameterizedMessage parameterizedMessage = (ParameterizedMessage) message;
Object[] params = ();
if (params == null || <= 0) {
return logEvent;
}
Object[] newParams = new Object[];
for (int i = 0; i < ; i++) {
try {
if(params[i] instanceof JSONObject){
JSONObject jsonObject = (JSONObject) params[i];
// 处理json格式的日志
newParams[i] = encryptionJson(jsonObject, encryptionKeys);
} else{
newParams[i] = params[i];
}
} catch (Exception e) {
newParams[i] = params[i];
}
}
ParameterizedMessage m = new ParameterizedMessage((), newParams,
());
builder = ().setMessage(m);
return ();
} else if (message instanceof SimpleMessage) {
SimpleMessage newMessage = decryptionSimpleMessage((SimpleMessage) message);
builder = ().setMessage(newMessage);
return ();
} else {
return logEvent;
}
}
/**
* 单例模式创建(静态内部类模式)
*
* @return
*/
@PluginFactory
public static DataMaskingRewritePolicy createPolicy() {
return ;
}
private SimpleMessage decryptionSimpleMessage(SimpleMessage simpleMessage) {
String messsage = ();
String newMessage = messsage;
if (!(messsage)) {
boolean isContain = ().anyMatch(key -> (messsage, key));
// 包含敏感词
if (isContain) {
for (String key : encryptionKeyArrays) {
int keyLength = ();
// 敏感词
String targetStr = new String("<" + key + ">");
StringBuffer targetSb = new StringBuffer(targetStr);
int startIndex = (targetStr);
/*
* 如<password>123456</password>替换为 <password>****</password>
*/
if (startIndex > -1 && ((ASTERISK).append("</").append(key).append(">").toString()) == -1) {
int endIndex = (targetStr);
if (endIndex > -1) {
newMessage = (0, startIndex + keyLength + 2) + ASTERISK
+ (endIndex, ());
}
}
/*
* 如password:123456替换为password:****
*/
targetStr = key + ":";
if ((targetStr) > -1 && (targetStr + ASTERISK) == -1) {
startIndex = (targetStr) + keyLength + 1;
String endMessage = (startIndex, ());
int endIndex = (",");
if (endIndex > -1) {
newMessage = (0, startIndex) + ASTERISK
+ (endIndex, ());
} else if ((",") == -1 && ("=") == -1) {
newMessage = (0, startIndex) + ASTERISK;
}
}
/*
* 如password=123456替换为password=****
*/
if ((key + "=") > -1 && (key + "=" + ASTERISK) == -1) {
startIndex = (key + "=") + keyLength + 1;
String endMessage = (startIndex, ());
int endIndex = (",");
if (endIndex > -1) {
newMessage = (0, startIndex) + ASTERISK
+ (endIndex, ());
} else if ((",") == -1 && ("=") == -1) {
newMessage = (0, startIndex) + ASTERISK;
}
}
/*
* 如"password":"123456" 替换为"password":"****"
*/
String qmKey = QUOTATION_MARK + key + QUOTATION_MARK + ":";
if ((qmKey) > -1 && (qmKey + ASTERISK) == -1) {
startIndex = (qmKey) + keyLength + 3;
String endMessage = (startIndex, ());
int endIndex = (",");
if (endIndex > -1) {
newMessage = (0, startIndex) + QUOTATION_MARK + ASTERISK
+ QUOTATION_MARK + (endIndex, ());
} else if ((",") == -1 && ("=") == -1) {
newMessage = (0, startIndex) + QUOTATION_MARK + ASTERISK
+ QUOTATION_MARK;
}
}
}
return new SimpleMessage(newMessage);
}
}
return simpleMessage;
}
/**
* 处理日志,递归获取值
*
*/
private Object encryptionJson(Object object, List<String> encryptionKeys) {
String jsonString = (object);
if (object instanceof JSONObject) {
JSONObject json = (jsonString);
boolean isContain = ().anyMatch(key -> (jsonString, key));
if (isContain) {
// 判断当前字符串中有没有key值
Set<String> keys = ();
(key -> {
boolean result = ().anyMatch(ekey -> (ekey, key));
if (result) {
String value = (key);
// 加密
(key, ASTERISK);
} else {
(key, encryptionJson((key), encryptionKeys));
}
});
}
return json;
} else if (object instanceof JSONArray) {
JSONArray jsonArray = (jsonString);
for (int i = 0; i < (); i++) {
JSONObject jsonObject = (i);
// 转换
(i, encryptionJson(jsonObject, encryptionKeys));
}
return jsonArray;
}
return object;
}
}
2、修改配置,增加Rewrite重新日志配置
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<properties>
<!--设置日志文件存储路径为tomcat/logs/${APP_NAME}-->
<Property name="LOG_FILE_PATH">${sys:}/logs/${APP_NAME}</Property>
<!--设置日志输出格式-->
<Property name="PATTERN_FORMAT">[%t][%d{yyyyMMdd HH:mm:ss}][%-5p][%c{3}:%L] - %m%n</Property>
<Property name="LOG_LEVEL">info</Property>
</properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[Shop][%t][%d{yyyyMMdd HH:mm:ss}][%-5p][%c{3}:%L] - %m%n"/>
</Console>
<!-- 配置重写日志 -->
<Rewrite name="rewrite">
<DataMaskingRewritePolicy/>
<AppenderRef ref="Console"/>
<!-- 将catalina日志重写 -->
<!-- <AppenderRef ref="Catalina"/> -->
</Rewrite>
</Appenders>
<Loggers>
<logger name="" level="${LOG_LEVEL}">
<AppenderRef ref="Console" />
</logger>
<logger name="" level="${LOG_LEVEL}">
<AppenderRef ref="rewrite" />
</logger>
<Root level="info">
<!-- <AppenderRef ref="Console" /> -->
<!-- <AppenderRef ref="RollingInfoFile" /> -->
</Root>
</Loggers>
</configuration>
3、单元测试
@
public void testLog4j2JSONString(){
Runtime r = ();
();//计算内存前先垃圾回收一次
long start = ();//开始Time
long startMem = (); // 开始Memory
for (int i = 0; i < 1; i++) {
("\"password\":\"123456\",\"expireYear\":\"2025\",\"expireMonth\":\"10\",\"cvv\":\"844\"");
}
//输出
long endMem =(); // 末尾Memory
long end = ();//末尾Time
("用时消耗: "+(end - start)+"ms");
("内存消耗: "+((startMem- endMem)/1024)+"KB");
}
二、第二种方案:自定义脱敏工具类
import ;
import ;
import ;
import .;
import ;
/**
* String数据脱敏处理
*
*/
public class StringMaskUtil {
/*
* 脱敏符号
*/
private static final String ASTERISK = "****";
// 需要脱敏的字段配置数组
private static final String[] encryptionKeyArrays = { "password", "expireYear", "expireMonth","cvv","number"};
/*
* 引号
*/
private static final String QUOTATION_MARK = "\"";
// 将数组转换为集合,方便查找
private static final List<String> encryptionKeys = new ArrayList<>();
// 使用静态内部类创建对象,节省空间
private static class StaticStringMaskUtil {
private static final StringMaskUtil stringMaskUtil = new StringMaskUtil();
}
public StringMaskUtil() {
if ((encryptionKeys)) {
((encryptionKeyArrays));
}
}
public static StringMaskUtil instance(){
return ;
}
/**
* 支持的格式
*
* 1、<key>value</key>
* 2、key=value,key1=value1
* 3、key:value,key1:value1
* 4、"key":"value","key1":"value1"
*
* @param messsage
* @return
*/
public String mask(String messsage){
String newMessage = messsage;
if (!(messsage)) {
boolean isContain = ().anyMatch(key -> (messsage, key));
// 包含敏感词
if (isContain) {
for (String key : encryptionKeys) {
int keyLength = ();
// 敏感词
String targetStr = new String("<" + key + ">");
StringBuffer targetSb = new StringBuffer(targetStr);
int startIndex = (targetStr);
/*
* 如<password>123456</password>替换为 <password>****</password>
*/
if (startIndex > -1 && ((ASTERISK).append("</").append(key).append(">").toString()) == -1) {
int endIndex = ("</" + key + ">");
if (endIndex > -1) {
newMessage = (0, startIndex + keyLength + 2) + ASTERISK
+ (endIndex, ());
}
}
/*
* 如,password:123456替换为password:****
*/
targetStr =","+ key + ":";
if ((targetStr) > -1 && (targetStr + ASTERISK) == -1) {
startIndex = (targetStr) + keyLength + 2;
String endMessage = (startIndex, ());
int endIndex = (",");
if (endIndex > -1) {
newMessage = (0, startIndex) + ASTERISK
+ (endIndex, ());
} else if ((",") == -1 && ("=") == -1) {
newMessage = (0, startIndex) + ASTERISK;
}
}else if((key + ":")){
startIndex = keyLength + 1;
String endMessage = (startIndex, ());
int endIndex = (",");
if (endIndex > -1) {
newMessage = (0, startIndex) + ASTERISK
+ (endIndex, ());
} else if ((",") == -1 && ("=") == -1) {
newMessage = (0, startIndex) + ASTERISK;
}
}
/*
* 如,password=123456替换为password=****
*/
targetStr =","+ key + "=";
if ((targetStr) > -1 && (key + "=" + ASTERISK) == -1) {
startIndex = (targetStr) + keyLength + 2;
String endMessage = (startIndex, ());
int endIndex = (",");
if (endIndex > -1) {
newMessage = (0, startIndex) + ASTERISK
+ (endIndex, ());
} else if ((",") == -1 && ("=") == -1) {
newMessage = (0, startIndex) + ASTERISK;
}
}else if((key + "=")){
startIndex = keyLength + 1;
String endMessage = (startIndex, ());
int endIndex = (",");
if (endIndex > -1) {
newMessage = (0, startIndex) + ASTERISK
+ (endIndex, ());
} else if ((",") == -1 && ("=") == -1) {
newMessage = (0, startIndex) + ASTERISK;
}
}
/*
* 如"password":"123456" 替换为"password":"****"
*/
String qmKey = QUOTATION_MARK + key + QUOTATION_MARK + ":";
if ((qmKey) > -1 && (qmKey + ASTERISK) == -1) {
startIndex = (qmKey) + keyLength + 3;
String endMessage = (startIndex, ());
int endIndex = (",");
if (endIndex > -1) {
newMessage = (0, startIndex) + QUOTATION_MARK + ASTERISK
+ QUOTATION_MARK + (endIndex, ());
} else if ((",") == -1 && ("=") == -1) {
newMessage = (0, startIndex) + QUOTATION_MARK + ASTERISK
+ QUOTATION_MARK;
}
}
}
}
}
return newMessage;
}
}
自定义脱敏工具类单元测试
@
public void testLog4jString(){
Runtime r = ();
();//计算内存前先垃圾回收一次
long start = ();//开始Time
long startMem = (); // 开始Memory
for (int i = 0; i < 1; i++) {
(().mask("oldpassword=123456,expireYear=2022,expireMonth=12,cvv=844"));
(().mask("password:123456,aaexpireYear:2022,expireMonth:12,cvv:844"));
(().mask("password=123456,expireYear=2022,expireMonth=12,cvv=844"));
(().mask("\"oldpassword\":\"123456\",\"expireYear\":\"2025\",\"expireMonth\":\"10\",\"cvv\":\"844\""));
(().mask("<password>123456</password><expireYear>123456</expireYear><expireMonth>123456</expireMonth><cvv>123456</cvv>"));
}
//输出
long endMem =(); // 末尾Memory
long end = ();//末尾Time
("用时消耗: "+(end - start)+"ms");
("内存消耗: "+((startMem- endMem)/1024)+"KB");
}