日志里的敏感信息还在打明文?3 种日志脱敏方案任你选

关注公众号【1024个为什么】,及时接收最新推送文章!

下面链接文章中重新做了梳理,补充了基于 log4j  的解决方案,建议大家阅读最新文章。

《一次性解决打日志时的4个重复低效场景(日志脱敏、日期格式化、json序列化)》

背景

我们打的日志中经常包含姓名、手机号、银行卡号等敏感信息,如果不做任何处理,就会以明文的形式展示在日志中,存在安全风险。

像下面这样:

5d99b4681b7fd2aa7fe9551775c082b5.png

我们需要一种能自动帮我们脱敏的工具,效果如下:

39f27124e109403843abab637db8b451.png

方案1 - 基于 logback

我们得先搞清楚消息内容是在哪里处理的,也就是配置文件中这个占位符的内容:

191fd9b15563920a8a693bc21329da68.png

对应到源码是这里 ch.qos.logback.classic.PatternLayout :

3519b2a3dd9ec032143a890d3b1bc8fa.png

这里可以看出来都是 通过这个类处理的  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

再从 logback.xml 中加上这行配置

<conversionRule conversionWord="msg" converterClass="com.TuoMinConverter "/>

两个属性说明一下:

conversionWord:不能乱配,要和图里红框内的内容一致。

f88bc8c3ac491141ce87623781770bfc.png

converterClass:就配我们刚写的转换类 TuoMinConverter 。

缺点:

这种方案处理的是整条日志内容,一般是通过正则匹配再替换内容,不能精准替换指定属性的内容,还存在误杀情况。

59b02cbb2d522c7baf5bc1e3e17eee01.png

这个例子中的 [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)  就不用再传其他参数了,也支持集合。

看看效果

918eac86c37a13f3168e936f261ace1b.png

是不是很过瘾,以后就连日志参数都不用每次加一层 JSON.toJSONString()了,随意往里面扔各种对象。

缺点:

这么完美的方案哪还有缺点。

优点:

时间戳、脱敏 一站式解决,好不好用一看就知道。

扯两句

只要是重复的代码,就能往外抽

以后日志就这么打,让他们好奇去吧

原创不易,多多关注,一键三连,感谢支持!

参考文献:

https://blog.csdn.net/weixin_43897590/article/details/115729271

https://blog.csdn.net/u011277745/article/details/108793590

  • 1
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值