Log4j2日志脱敏

1 篇文章 0 订阅
1 篇文章 0 订阅

Log42 的日志脱敏

在这里插入图片描述

Log42 的日志脱敏

最近遇到公司的敏感数据脱敏,就想着也不能挨个的日志去找他的,太多了。后来想到logger.info() 这个方法,就在网上找了找对应的信息,可以。有这样实现的,但是实现的功能有的有点太简单,不能变通,有的写得不清楚,我觉得我自己也记个记录吧,也和大家分享下,后期性能优化还没想到。如果有更好的思路,请留言。

代码部分

原来的log4j2 的XML默认的是这样的 。发现在这个PatternLayout中有个定义格式的类。。。


 1  <?xml version="1.0" encoding="UTF-8"?>
 2  <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
 3  <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
 4  <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
 5  <configuration status="WARN" monitorInterval="30">
 6      <!--先定义所有的appender-->
 7      <appenders>
 8      <!--这个输出控制台的配置-->
 9          <console name="Console" target="SYSTEM_OUT">
10          <!--输出日志的格式-->
11              <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
12          </console>
13      <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
14      <File name="log" fileName="log/test.log" append="false">
15         <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
16      </File>
17      <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
18          <RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
19                       filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
20              <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->        
21              <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
22              <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
23              <Policies>
24                  <TimeBasedTriggeringPolicy/>
25                  <SizeBasedTriggeringPolicy size="100 MB"/>
26              </Policies>
27          </RollingFile>
28          <RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
29                       filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
30              <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
31              <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
32              <Policies>
33                  <TimeBasedTriggeringPolicy/>
34                  <SizeBasedTriggeringPolicy size="100 MB"/>
35              </Policies>
36          <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
37              <DefaultRolloverStrategy max="20"/>
38          </RollingFile>
39          <RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
40                       filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
41              <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
42              <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
43              <Policies>
44                  <TimeBasedTriggeringPolicy/>
45                  <SizeBasedTriggeringPolicy size="100 MB"/>
46              </Policies>
47          </RollingFile>
48      </appenders>
49      <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
50      <loggers>
51          <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
52          <logger name="org.springframework" level="INFO"></logger>
53          <logger name="org.mybatis" level="INFO"></logger>
54          <root level="all">
55              <appender-ref ref="Console"/>
56              <appender-ref ref="RollingFileInfo"/>
57              <appender-ref ref="RollingFileWarn"/>
58              <appender-ref ref="RollingFileError"/>
59          </root>
60      </loggers>
61  </configuration>

好的,开始写代码。
在其中我们找到了这个类 :PatternLayout 输出日志的格式 ,他便是在输出前进行格式化的类,所以我们继承他是不是就可以自己去定义应该过滤的敏感信息了啊?然后,我继承了这个类:

package com.davinqi.logger;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * log4j2 脱敏插件
 *
 * @Author qix*****
 * @Date 2020-08-20 15:57
 * @Version 1.0
 **/
@Plugin(name = "CustomPatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public class CustomPatternLayout extends AbstractStringLayout {


    public final static Logger logger = LoggerFactory.getLogger(CustomPatternLayout.class);
    private PatternLayout patternLayout;


    protected CustomPatternLayout(Charset charset, String pattern) {
        super(charset);
        patternLayout = PatternLayout.newBuilder().withPattern(pattern).build();
        initRule();
    }

    /**
     * 要匹配的正则表达式map
     */
    private static Map<String, Pattern> REG_PATTERN_MAP = new HashMap<>();
    private static Map<String, String> KEY_REG_MAP = new HashMap<>();


    private void initRule() {
        try {
            if (MapUtils.isEmpty(MyLog4jRule.regularMap)) {
                return;
            }
            MyLog4jRule.regularMap.forEach((a, b) -> {
                if (StringUtils.isNotBlank(a)) {
                    Map<String, String> collect = Arrays.stream(a.split(",")).collect(Collectors.toMap(c -> c, w -> b, (key1, key2) -> key1));
                    KEY_REG_MAP.putAll(collect);
                }
                Pattern compile = Pattern.compile(b);
                REG_PATTERN_MAP.put(b, compile);
            });

        } catch (Exception e) {
            logger.info(">>>>>> 初始化日志脱敏规则失败 ERROR:{}", e);
        }

    }

    /**
     * 1.判断配置文件中是否已经配置需要脱敏字段
     * 2.判断内容是否有需要脱敏的敏感信息
     * 2.1 没有需要脱敏信息直接返回
     * 2.2 处理: 身份证 ,姓名,手机号敏感信息
     *
     * @param @param  msg
     * @param @return
     * @return String
     */
    public String hideMarkLog(String logStr) {
        try {
            //1.判断配置文件中是否已经配置需要脱敏字段
            if (StringUtils.isBlank(logStr) || MapUtils.isEmpty(KEY_REG_MAP) || MapUtils.isEmpty(REG_PATTERN_MAP)) {
                return logStr;
            }
            //2.判断内容是否有需要脱敏的敏感信息
            Set<String> charKeys = KEY_REG_MAP.keySet();
            for (String key : charKeys) {
                if (logStr.contains(key)) {
                    String regExp = KEY_REG_MAP.get(key);
                    logStr = matchingAndEncrypt(logStr, regExp, key);
                }
            }
            return logStr;
        } catch (Exception e) {
            logger.info(">>>>>>>>> 脱敏处理异常 ERROR:{}", e);
            //如果跑出异常为了不影响流程,直接返回原信息
            return logStr;
        }
    }

    /**
     * 正则匹配对应的对象。
     *
     * @param msg
     * @param regExp
     * @return
     */
    private static String matchingAndEncrypt(String msg, String regExp, String key) {
        Pattern pattern = REG_PATTERN_MAP.get(regExp);
        if (pattern == null) {
            logger.info(">>> logger 没有匹配到对应的正则表达式 ");
            return msg;
        }
        Matcher matcher = pattern.matcher(msg);
        int length = key.length() + 5;
        boolean contains = MyLog4jRule.USER_NAME_STR.contains(key);
        String hiddenStr = "";
        while (matcher.find()) {
            String originStr = matcher.group();
            if (contains) {
                // 计算关键词和需要脱敏词的距离小于5。
                int i = msg.indexOf(originStr);
                if (i < 0) {
                    continue;
                }
                int span = i - length;
                int startIndex = span >= 0 ? span : 0;
                String substring = msg.substring(startIndex, i);
                if (StringUtils.isBlank(substring) ||  !substring.contains(key)) {
                    continue;
                }

//                // 正则 但是影响性能
//                String userNearKey = "(([\\s\\S]*)(" + key + ")(\\S){0,6}(" + originStr + ")([\\s\\S]*))";
//                Pattern p = Pattern.compile(userNearKey);
//                Matcher m = p.matcher(msg);
//                if (!m.matches()) {
//                    continue;
//                }

                hiddenStr = hideMarkStr(originStr);
                msg = msg.replace(originStr, hiddenStr);
            } else {
                hiddenStr = hideMarkStr(originStr);
                msg = msg.replace(originStr, hiddenStr);
            }

        }
        return msg;
    }

    /**
     * 标记敏感文字规则
     *
     * @param needHideMark
     * @return
     */
    private static String hideMarkStr(String needHideMark) {
        if (StringUtils.isBlank(needHideMark)) {
            return "";
        }
        int startSize = 0, endSize = 0, mark = 0, length = needHideMark.length();

        StringBuffer hideRegBuffer = new StringBuffer("(\\S{");
        StringBuffer replaceSb = new StringBuffer("$1");

        if (length > 4) {
            int i = length / 3;
            startSize = i;
            endSize = i;
        } else {
            startSize = 1;
            endSize = 0;
        }

        mark = length - startSize - endSize;
        for (int i = 0; i < mark; i++) {
            replaceSb.append("*");
        }
        hideRegBuffer.append(startSize).append("})\\S*(\\S{").append(endSize).append("})");
        replaceSb.append("$2");
        needHideMark = needHideMark.replaceAll(hideRegBuffer.toString(), replaceSb.toString());
        return needHideMark;
    }


    /**
     * LOG4J2-783 use platform default by default,
     * so do not specify defaultString for charset
     *
     * @param pattern
     * @param charset
     * @return
     */
    @PluginFactory
    public static Layout createLayout(@PluginAttribute(value = "pattern") final String pattern,
                                      @PluginAttribute(value = "charset") final Charset charset) {
        return new CustomPatternLayout(charset, pattern);
    }


    @Override
    public String toSerializable(LogEvent event) {
        return hideMarkLog(patternLayout.toSerializable(event));
    }

}

一定要把 正则规则Pattern初始化了,要不然你得日志非常的耗时,业务流程也很慢。-||

package com.davinqi.logger;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author qix*********
 * @Date 2020-08-20 16:11
 * @Version 1.0
 * <p>
 * 现在拦截加密的日志有三类:
 * 1,身份证
 * 2,姓名
 * 3,身份证号
 **/
public class MyLog4jRule {

    /**
     * 正则匹配 关键词 类别
     */
    public static Map<String, String> regularMap = new HashMap<>();
    /**
     * TODO  可配置
     * 此项可以后期放在配置项中
     */
    public static final String USER_NAME_STR = "Name,name,联系人,姓名";
    public static final String USER_IDCARD_STR = "empCard,idCard,身份证,证件号";
    public static final String USER_PHONE_STR = "mobile,Phone,phone,电话,手机";

    /**
     * "(\\d{6}\\d{4}(([0][1-9])|([1][0-2]))(([0][1-9])|([1][0-9])|([2][0-9])|([3][0-1]))[0-9]{3}[xX0-9]) | (\\d{6}\\d{2}(([0][1-9])|([1][0-2]))(([0][1-9])|([1][0-9])|([2][0-9])|([3][0-1]))[0-9]{3})";
     */
    private static String IDCARD_REGEXP = "(\\d{17}[0-9Xx]|\\d{14}[0-9Xx])";
    private static String USERNAME_REGEXP = "[\\u4e00-\\u9fa5]{2,4}";
    private static String PHONE_REGEXP = "(?<!\\d)(?:(?:1[3456789]\\d{9})|(?:861[356789]\\d{9}))(?!\\d)";

    static {
        regularMap.put(USER_NAME_STR, USERNAME_REGEXP);
        regularMap.put(USER_IDCARD_STR, IDCARD_REGEXP);
        regularMap.put(USER_PHONE_STR, PHONE_REGEXP);
    }

}

此类里放置了定义的敏感key和正则表达式匹配,我自己定义的规则是:脱敏的日志中包含这个这些关键词,然后正则匹配这些,脱敏的词汇,进行自己定义的格式脱敏。因为汉字太多了。我限制了 在关键词后5个以内有汉字能匹配的,才去脱敏。

最后吧想脱敏级别的日志替换成自己定义的PatternLayout

PatternLayout  -> CustomPatternLayout

这块的关键词匹配会随着数量变大时间复杂度,也会变大看看大家有什么优化的点子呢?

by davinqi

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,以下是一个简单的log4j2日志脱敏的过滤器示例: 首先,在log4j2配置文件中添加以下内容: ```xml <Configuration> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> <filters> <Filter type="Script" language="groovy" script="if (logEvent.getMessage().getFormattedMessage().contains('password')) {return Result.DENY;} else {return Result.NEUTRAL;}"/> </filters> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration> ``` 其中,filters标签用于添加过滤器,type属性为“Script”,表示使用脚本过滤器,language属性为“groovy”,表示使用Groovy语言编写脚本。 然后,在script属性中添加以下Groovy脚本: ```groovy if (logEvent.getMessage().getFormattedMessage().contains('password')) { def message = logEvent.getMessage().getFormattedMessage().replaceAll(/password=\S+/, 'password=******') return new LogEvent(loggerName, logEvent.getMarker(), loggerFqcn, logEvent.getLevel(), message, logEvent.getThrown(), logEvent.getContextMap(), logEvent.getContextStack(), logEvent.getThreadName(), logEvent.getSource(), logEvent.getTimeMillis()) } else { return logEvent } ``` 该脚本会检查日志消息中是否包含“password”关键字,如果包含,则将“password”后面的字符替换为“******”,然后返回修改后的LogEvent对象。如果不包含,则直接返回原始的LogEvent对象。 这样配置后,当日志中包含“password”关键字时,日志消息中的“password”后面的字符就会被替换为“******”,从而实现了日志脱敏的功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值