现在项目中没有对日志脱敏,于是我萌生了研究日志脱敏的想法。之前对日志系统没有深入了解过,总结了一下日志的知识点:
- 日志基础研究脑图
- 日志基础框架图
- 主要参考资料:
源码分析:
https://cloud.tencent.com/developer/article/1605924?from=article.detail.1442406
https://cloud.tencent.com/developer/article/1605922?from=article.detail.1605924
日志脱敏:
https://logging.apache.org/log4j/2.x/manual/appenders.html 中的RewriteAppender
首先看一下最终脱敏效果:
具体步骤如下:
方案是参考官网的RewriteAppender,实现日志脱敏。
1.重写RewritePolicy中的rewrite方法,在每次打印日志之前进行一次数据过滤,将敏感字段进行加密。
package com.zxy.demo.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* DataMaskingRewritePolicy
*
* @author zhouxy
* @date 2022/2/23
**/
@Plugin(name = "DataMaskingRewritePolicy", category = "Core", elementType = "rewritePolicy", printObject = true)
public class DataMaskingRewritePolicy implements RewritePolicy {
//使用静态内部类创建对象,节省空间
private static class StaticDataMaskingRewritePolicy {
private static final DataMaskingRewritePolicy dataMaskingRewritePolicy = new DataMaskingRewritePolicy();
}
//需要加密的字段配置数组
private static final String[] encryptionKeyArrays = {"password"};
//将数组转换为集合,方便查找
private static final List<String> encryptionKeys = new ArrayList<>();
public DataMaskingRewritePolicy() {
if (CollectionUtils.isEmpty(encryptionKeys)) {
encryptionKeys.addAll(Arrays.asList(encryptionKeyArrays));
}
}
/**
* 日志修改方法,可以对日志进行过滤,修改
*
* @param logEvent
* @return
*/
@Override
public LogEvent rewrite(LogEvent logEvent) {
if (!(logEvent instanceof Log4jLogEvent)) {
return logEvent;
}
Log4jLogEvent log4jLogEvent = (Log4jLogEvent) logEvent;
Message message = log4jLogEvent.getMessage();
if (!(message instanceof ParameterizedMessage)) {
return logEvent;
}
ParameterizedMessage parameterizedMessage = (ParameterizedMessage) message;
Object[] params = parameterizedMessage.getParameters();
if (params == null || params.length <= 0) {
return logEvent;
}
Object[] newParams = new Object[params.length];
for (int i = 0; i < params.length; i++) {
try {
JSONObject jsonObject = JSON.parseObject(params[i].toString());
//处理json格式的日志
newParams[i] = encryption(jsonObject, encryptionKeys);
} catch (Exception e) {
newParams[i] = params[i];
}
}
ParameterizedMessage m = new ParameterizedMessage(parameterizedMessage.getFormat(), newParams, parameterizedMessage.getThrowable());
Log4jLogEvent.Builder builder = log4jLogEvent.asBuilder().setMessage(m);
return builder.build();
}
/**
* 单例模式创建(静态内部类模式)
*
* @return
*/
@PluginFactory
public static DataMaskingRewritePolicy createPolicy() {
return StaticDataMaskingRewritePolicy.dataMaskingRewritePolicy;
}
/**
* 处理日志,递归获取值
*
* @Author zhouxy
*/
private Object encryption(Object object, List<String> encryptionKeys) {
String jsonString = JSON.toJSONString(object);
if (object instanceof JSONObject) {
JSONObject json = JSON.parseObject(jsonString);
boolean isContain = encryptionKeys.stream().anyMatch(key -> StringUtils.contains(jsonString, key));
if (isContain) {
//判断当前字符串中有没有key值
Set<String> keys = json.keySet();
keys.forEach(key -> {
boolean result = encryptionKeys.stream().anyMatch(ekey -> Objects.equal(ekey, key));
if (result) {
String value = json.getString(key);
//加密
json.put(key, "****");
} else {
json.put(key, encryption(json.get(key), encryptionKeys));
}
});
}
return json;
} else if (object instanceof JSONArray) {
JSONArray jsonArray = JSON.parseArray(jsonString);
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
//转换
jsonArray.set(i, encryption(jsonObject, encryptionKeys));
}
return jsonArray;
}
return object;
}
}
2.log4j2.xml配置,将重写类配置到log4j2.xml中,使用<Rewrite>标签。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Properties>
<!-- 日志文件存放目录 -->
······
</Properties>
<Appenders>
<!-- 控制台输出日志 -->
<Console name="Console" target="SYSTEM_OUT" follow="true">
······
</Console>
<!-- 配置重写日志 -->
<Rewrite name="rewrite">
<DataMaskingRewritePolicy/>
<AppenderRef ref="Console"/>
<!-- 将catalina日志重写 -->
<AppenderRef ref="Catalina"/>
</Rewrite>
<!-- Catalina日志 -->
<RollingFile name="Catalina" fileName="${LOG_HOME}/catalina/catalina.log"
filePattern="${LOG_HOME}/catalina/catalina-%d{yyyy-MM-dd}-%i.log.gz">
······
</RollingFile>
</Appenders>
<Loggers>
······
<Root level="INFO">
<AppenderRef ref="Catalina"/>
<!-- 打印重写日志 -->
<AppenderRef ref="rewrite"/>
</Root>
</Loggers>
</Configuration>
过程中遇到的问题:
1.在自己的项目中搭建日志脱敏的代码,测试没有问题。但是将该日志脱敏代码放到公司的项目中,报如下错误:
2022-02-23 18:33:24,425 main ERROR Rewrite contains an invalid element or attribute "DataMaskingRewritePolicy"
日志脱敏代码创建不成功。没办法实现日志脱敏功能。经过排查,发现是maven依赖与当前类有冲突:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<!--annotationProcessorPaths这个引入注释掉之后,DataMaskingRewritePolicy就可以创建成功-->
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
原因的话,我估计是lombok中也有涉及到日志的功能,可能版本不对应,导致不支持RewriteAppender类
结论:
在研究日志脱敏技术过程中,网上的文献比较少,只能依靠看官网代码。因为之前很少去研究log4j2的技术,在看日志脱敏的技术时比较难理解思想,很难去实施。因此在感觉自己很难理解的时候,我去巩固了一下log4j2技术的基础,理解了它的思想。在后来去配置以及排查问题的时候,这些基础思想起了很大的作用。
但是在我陆陆续续研究了两三个星期了之后,由于我们公司的日志系统是一个公共的系统,没办法针对不同的系统做日志脱敏的配置。最后只能放弃了,这是很遗憾的事情。回想这个过程,可能刚开始的方向没把握好,如果只是对密码password进行日志脱敏的话,可以让前端传过来加密密码就可以了。但是对于B端对B端来说,是有参考意义的。