关注公众号【1024个为什么】,及时接收最新推送文章!
下面链接文章中重新做了梳理,补充了基于 log4j 的解决方案,建议大家阅读最新文章。
《一次性解决打日志时的4个重复低效场景(日志脱敏、日期格式化、json序列化)》
背景
我们打的日志中经常包含姓名、手机号、银行卡号等敏感信息,如果不做任何处理,就会以明文的形式展示在日志中,存在安全风险。
像下面这样:
我们需要一种能自动帮我们脱敏的工具,效果如下:
方案1 - 基于 logback
我们得先搞清楚消息内容是在哪里处理的,也就是配置文件中这个占位符的内容:
对应到源码是这里 ch.qos.logback.classic.PatternLayout :
这里可以看出来都是 通过这个类处理的 ch.qos.logback.classic.pattern.MessageConverter。
继续看一下这个类的逻辑:
public class MessageConverter extends ClassicConverter {
public MessageConverter() {
}
public String convert(ILoggingEvent event) {
return event.getFormattedMessage();
}
}
只有一个 convert 方法,想要修改日志的内容,重载此方法即可。
public class TuoMinConverter extends MessageConverter {
@Override
public String convert(ILoggingEvent event){
try {
String msg = event.getFormattedMessage();
// 这里自定义脱敏的逻辑
return doTuoMin(msg);
}catch (Exception e){
return super.convert(event);
}
}
}
可以参考这位朋友的写法:
https://blog.csdn.net/u011277745/article/details/108793590文章来源地址https://www.toymoban.com/news/detail-401858.html
再从 logback.xml 中加上这行配置
<conversionRule conversionWord="msg" converterClass="com.TuoMinConverter "/>
两个属性说明一下:
conversionWord:不能乱配,要和图里红框内的内容一致。
converterClass:就配我们刚写的转换类 TuoMinConverter 。
缺点:
这种方案处理的是整条日志内容,一般是通过正则匹配再替换内容,不能精准替换指定属性的内容,还存在误杀情况。
这个例子中的 [country : 中国] 就被误杀。
只要满足我们定义的正则,就会被误杀,就会出现很多不该被脱敏的也脱敏了的情况。
方案2 - 基于 fastjson
思路是把要打印的内容脱敏后,再交给日志框架。
这里又回到上篇文章里《日志里打出来的都是时间戳?教你一行代码搞定它》说的 JSON.toJSONString()。
fastjson 提供了很多 Filter,我们这里的场景是在序列化成字符串的时候替换掉 json 键值对里 value 的值,所以要用到这个 Filter。
com.alibaba.fastjson.serializer.ValueFilter
public interface ValueFilter extends SerializeFilter {
Object process(Object object, String name, Object value);
}
介绍一下 process 方法的 3 个参数:
object:本次用不到,不用关注
name:可以理解为当前处理 java 对象的属性名,比如只处理手机号(phone)的话就可以根据 name 过滤
if(name.equals("phone")){
value = 脱敏逻辑(value);
}
value:属性对应的值,也就是我们真正要脱敏的内容
实现这个接口,在 process 方法中编写脱敏逻辑。
public class DataMaskFilter implements ValueFilter {
public static DataMaskFilter instance(){
return new DataMaskFilter();
}
@Override
public Object process(Object object, String name, Object value) {
for (DataMaskRuleEnum rule : DataMaskRuleEnum.values()) {
List<String> fields = Arrays.asList(rule.fieldName.split("\\|"));
if(fields.contains(name.toUpperCase())){
value = value.toString().replaceAll(rule.regular, rule.result);
}
}
return value;
}
}
这里我把脱敏规则单独放到了一个枚举类中,因为在实际工作中,可能 phone, phoneNo, phoneNum 都表示手机号,抽到枚举中单独管理方便添加新的属性名。只要属性名在枚举配置里能找到,就对它脱敏。
public enum DataMaskRuleEnum {
ID_CARD("身份证号脱敏", "IDCARD", "^(\\d{4})\\d+(\\d{4})$", "$1****$2"),
PHONE("手机号脱敏", "PHONE|BANKPHONE", "^(\\d{3})\\d+(\\d{4})$", "$1****$2"),
BANK_CARD("银行卡号脱敏", "CARDNUM", "^(\\d{4})\\d+(\\d{4})$", "$1****$2"),
NAME("姓名脱敏", "NAME|REALNAME|WORKERNAME", "^([\\u4E00-\\u9FA5]{1,3})([\\u4E00-\\u9FA5])$", "**$2")
;
DataMaskRuleEnum(String description, String fieldName, String regular, String result){
this.description = description;
this.fieldName = fieldName;
this.regular = regular;
this.result = result;
}
/**
* 脱敏规则描述
*/
public String description;
/**
* 要脱敏的属性名
*/
public String fieldName;
/**
* 脱敏规则
*/
public String regular;
/**
* 脱敏结果
*/
public String result;
}
使用:
logger.info("保存银行卡操作日志信息, cardLogDto={}",
JSON.toJSONString(cardLogDto, DataMaskFilter.instance()));
优点:
可以精准脱敏,具体到指定的属性名,基本不存在误杀情况(除非属性名起的很怪)。
缺点:
每次打日志的时候都要考虑是否传 SerializeFilter 这个参数。
方案3 - 基于 logback + fastjson
这个方案是集前两个方案的优点于一身。
思路是把 fastjson 的脱敏,嵌套在 logback 的 MessageConverter 中。
public class TuoMinConverter extends MessageConverter {
@Override
public String convert(ILoggingEvent event){
try {
return doTuoMin(event);
}catch (Exception e){
return super.convert(event);
}
}
private String doTuoMin(ILoggingEvent event){
try {
Object[] objects = Stream.of(event.getArgumentArray()).map(obj -> {
String msg;
if (obj instanceof String) {
// String 类型直接打印
msg = obj.toString();
} else {
// 其他类型 通过 fastjson Filter 功能脱敏后转成 json 字符串
msg = JSON.toJSONString(obj);
}
return msg;
}).toArray();
return MessageFormatter.arrayFormat(event.getMessage(), objects).getMessage();
} catch (Exception e) {
return event.getMessage();
}
}
}
两个地方重点说明一下:
event.getArgumentArray() 是获取到 logger.info() 方法中的所有参数,获取到参数之后,遍历每个参数,String 类型的直接打印,其他类型的变成 json 字符串后返回。得到的 objects 数组就是经过脱敏处理的 json 字符串对象集合。
MessageFormatter.arrayFormat(event.getMessage(), objects) 是把脱敏处理过的内容填充到日志的占位符中。
额外说明一下:
细心的朋友可能已经发现脱敏处理就一行 msg = JSON.toJSONString(obj); 怎么就脱敏了???
玄机还是在上篇文章《日志里打出来的都是时间戳?教你一行代码搞定它》里提到的 SerializeConfig 全局配置,这个里面不仅可以添加 ObjectSerializer 还可以添加 SerializeFilter 。
static {
SerializeConfig.getGlobalInstance().put(Date.class, new SimpleDateFormatSerializer("yyyy-MM-dd HH:mm:ss"));
SerializeConfig.getGlobalInstance().addFilter(User.class, DataMaskFilter.instance());
SerializeConfig.getGlobalInstance().addFilter(UserAccountDto.class, DataMaskFilter.instance());
}
可以为指定的 java 类型添加指定的 SerializeFilter ,需要为哪些 java 对象脱敏,加到这里就行,做到了更加精准的脱敏。
通过全局配置,toJSONString(obj) 就不用再传其他参数了,也支持集合。
看看效果
是不是很过瘾,以后就连日志参数都不用每次加一层 JSON.toJSONString()了,随意往里面扔各种对象。
缺点:
这么完美的方案哪还有缺点。
优点:
时间戳、脱敏 一站式解决,好不好用一看就知道。
扯两句
只要是重复的代码,就能往外抽
以后日志就这么打,让他们好奇去吧
原创不易,多多关注,一键三连,感谢支持!
参考文献:
https://blog.csdn.net/weixin_43897590/article/details/115729271文章来源:https://www.toymoban.com/news/detail-401858.html
https://blog.csdn.net/u011277745/article/details/108793590
到了这里,关于日志里的敏感信息还在打明文?3 种日志脱敏方案任你选的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!