日志打印敏感信息脱敏

1,508 阅读2分钟

以logback日志为例,log4j日志原理一致

原理

继承MessageConverter类,重写convert方法。获取到输出的日志信息,对日志信息使用正则匹配规则,获取到key-value键值对。根据指定的key键,修改其value值,使其日志信息关键词脱敏。

代码实现

是否开启脱敏字段、脱敏关键词字段可使用yml配置文件配置,使其更加灵活。

将重写好的日志脱敏类,放在logback.xml文件中即可,和<appender>标签是同级别存在的。

控制台以及日志文件输出就是脱敏的信息,保证的数据的安全性


    <!--自定义脱敏类-->
    <conversionRule conversionWord="msg" converterClass="org.example.config.SensitiveConverterFilter"/>


import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 日志脱敏过滤器
 *
 * @author 苦瓜不苦
 * @date 2022/11/2 10:59
 **/
public class SensitiveConverterFilter extends MessageConverter {


    // 正则匹配模式 - 该正则表达式第三个()可能无法匹配以某些特殊符号开头和结尾的(如果像密码这种字段,前后如果有很多特殊字段,则无法匹配)
    public static final Pattern REGEX_PATTERN = Pattern.compile("\\s*([\"]?[\\w]+[\"]?)(\\s*[:=]+[^\\u4e00-\\u9fa5@,.*{\\[\\w]*\\s*)([\\u4e00-\\u9fa5_\\-@.\\w]+)[\\W&&[^\\-@.]]?\\s*");

    // 是否开启脱敏
    private static final Boolean ENABLE = true;

    // 脱敏的关键词KEY
    private static final List<String> DESENSITIZATION_KEY_LIST = Arrays.asList("age", "name");

    @Override
    public String convert(ILoggingEvent event) {
        // 获取原始日志
        String message = event.getFormattedMessage();
        // 返回脱敏后的日志
        return filterSensitive(message);
    }

    /**
     * 敏感信息脱敏
     *
     * @param message 日志信息
     * @return
     */
    private String filterSensitive(String message) {
        // 判断是否开启
        if (!ENABLE) {
            return message;
        }
        // 判断是否填写关键词
        if (DESENSITIZATION_KEY_LIST.isEmpty()) {
            return message;
        }
        // 判断日志信息是否为空
        if (Objects.isNull(message) || message.isEmpty()) {
            return message;
        }
        // 正则匹配脱敏集合
        Map<String, String> map = matchMessage(DESENSITIZATION_KEY_LIST, message);
        // 替换脱敏的字符串
        return replaceMessage(map, message);
    }

    /**
     * 正则匹配脱敏集合
     *
     * @param keyList 脱敏的key集合
     * @param message 日志信息
     * @return
     */
    private static Map<String, String> matchMessage(List<String> keyList, String message) {
        Map<String, String> map = new HashMap<>();
        if (keyList == null || keyList.isEmpty()) {
            return map;
        }
        // 正则匹配
        Matcher matcher = REGEX_PATTERN.matcher(message);
        while (matcher.find()) {
            // 获取key值
            String key = matcher.group(1);
            // 修正key
            if (key.contains("\"")) {
                key = key.replace("\"", "");
            }
            // 判断是否需要脱敏
            if (keyList.contains(key)) {
                // 获取键值对
                String pair = matcher.group();
                // 获取value值
                String value = matcher.group(3);
                // value值脱敏
                String valueUpdate = desensitization(value);
                // 替换脱敏后的键值对
                String pairUpdate = pair.replace(value, valueUpdate);
                // 储存Map集合
                map.put(pair, pairUpdate);
            }
        }
        return map;
    }


    /**
     * 替换脱敏的字符串
     *
     * @param map     需要脱敏的集合
     * @param message 日志信息
     * @return
     */
    private static String replaceMessage(Map<String, String> map, String message) {
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            message = message.replace(key, value);
        }
        return message;
    }


    /**
     * 脱敏
     *
     * @param message 日志信息
     * @return
     */
    private static String desensitization(String message) {
        if (Objects.isNull(message) || message.isEmpty()) {
            return message;
        }
        int end = message.length();
        int start = end < 2 ? 0 : 1;
        end = end > 2 ? end - 1 : end;
        String substring = message.substring(start, end);
        StringBuilder bit = new StringBuilder();
        for (int i = 0; i < substring.length(); i++) {
            bit.append("*");
        }
        return message.replace(substring, bit.toString());
    }


}