应该每个公司都会有要求吧?日志不能打印用户的敏感信息,如身份证号,密码,银行卡号等。刚好最近在整理有关接口的事项,就顺便研究了下日志脱敏的几种实现。
一 日志配置文件设置pattern规则
这种呢,就是直接修改你的日志配置文件,比如你的是log4j2.xml,你可以这样写:
<Layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%traceId] [%t] %-5level %logger{1.} - %msg%n</pattern>
<replace regex='(IdNo|CertId|CertID|idCard)(":")(\d{6})\d{8,11}(\w{1})(")'
replacement="$1$2$3**************$4$5"/>
</Layout>
上面这个就可以过滤掉类似 "IdNo":"12345678901234567X",这样的字符。但是这个是原生的Pattern,只能写一个replace规则,比如有多个关键字需要匹配,如还想匹配mobile,需要制定不同的替换规则,这个我是没找到直接修改文件就可以实现的方法的。
二 自定义Pattern,实现多种替换规则
这种方法就可以弥补上面方法的不足,可以写多个关键字匹配替换规则,实现多种源信息的脱敏。
参考文档: https://blog.csdn.net/vcstrong/article/details/80527455
又已log4j2为例,实现的思路就是:
自己实现类,继承自org.apache.logging.log4j.core.layout.AbstractStringLayout
,在方法
@Override
public String toSerializable(final LogEvent event) {
final StringBuilder buf = new StringBuilder();
for (final PatternFormatter formatter : formatters) {
formatter.format(event, buf);
}
String str = buf.toString();
if (replace != null) {
str = replace.format(str);
}
return str;
}
中实现自定义的脱敏规则。其中replace也是自定义的,从日志配置文件中<replaces></replaces>中读取到匹配替换规则。
@PluginFactory
public static CustomRegexReplaces createRegexReplacement(
@PluginElement("replaces") final RegexReplacement[] replaces) {
if (replaces == null) {
log.info("no replaces is defined");
return null;
}
return new CustomRegexReplaces(replaces);
}
最后就可以根据自己的需求添加replace规则。
<CustomPatternLayout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%traceId] [%t] %-5level %logger{1.} - %msg%n</pattern>
<replaces>
<replace regex='(IdNo|CertId|CertID|idCard)(":")(\d{6})\d{8,11}(\w{1})(")'
replacement="$1$2$3**************$4$5"/>
<replace regex='(mobile|phone|phoneNo|tel)(":")(\d{3})\d{4}(\d{4})(")'
replacement="$1$2$3****$4"/>
</replaces>
</CustomPatternLayout>
三 自定义序列化规则
现在常用的数据格式是json,我一般用到的工具类是gson,所以可以考虑自己修改下gson的序列化方法,实现数据的脱敏。一开始的想实现的就是,我有一个类,有些属性加上了注解,比如标注它为身份证号,需要是用身份证号的脱敏方式,那么在用gson的转为json字符串方法时,这个注解被读到,并按照身份证脱敏的方式输出字符串。如下:
@DesensitizationType
public class User {
//自定义的脱敏注解,代表脱敏类型为password
@Desensitization(type = DesenseType.PASSWORD)
String password;
//自定义的脱敏注解,标注的其他脱敏方式的类和方法
@Desensitization(method = "trueName", className = "com.c4tman.play.logPrint.desense.MyDesensitization")
String name;
@Desensitization(type = DesenseType.MOBILE)
String mobile;
@Desensitization(type = DesenseType.ID_CARD)
String idCard;
@Desensitization(type = DesenseType.VERIFY_CODE)
String verifyCode;
.......
当将user实例json化,就会按照标注的脱敏规则去自行脱敏数据。
接下来就是要改造gson的序列化方法了,核心思想就是
private static Gson GSON = new GsonBuilder().registerTypeAdapterFactory(new MyTypeAdapterFactory()).create();
其中MyTypeAdapterFactory
是自己的实现,代码如下:
public class MyTypeAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<T> rawType = (Class<T>) typeToken.getRawType();
//如果这个类被@DesensitizationType标记,那么按自定义脱敏规则执行
boolean b = rawType.isAnnotationPresent(DesensitizationType.class);
if (b) {
return new MyTypeAdapter();
}
return null;
}
}
其中最重要的就是MyTypeAdapter
了,方法
public void write(JsonWriter jsonWriter, Object o) {}
就是序列化的规则,需要自己改写。我的思路就是获取object的每个属性,判断是否被@Desensitization
标记,如果标记了,就按照标记的规则去输出。
其中涉及到大量的反射,我的反射是真的学的渣/(ㄒoㄒ)/~~所以写的感觉就是坑坑洼洼,一点都不优雅,而且还有好多中数据类型打印不出来。比如Map,我无法通过传入一个Object,去判断为Map时,它的key,value的类型,进而进行脱敏。哎,反正就是功能实现的不全面,只能当作是一个练习反射的例子了。
所有的源码地址:https://github.com/C4TMAN/maskdata.git
写的很烂,bug多,功能也不全,仅供参考。同时欢迎交流,希望大佬教我怎么把Map给脱敏了(●ˇ∀ˇ●)。
最后这个的实现效果就是
原版:
脱敏版:
= =mingtianxie