logback自定义日志脱敏规则及脱敏开关(动态控制日志加密方式以及加密开关)

背景:公司日志数据不能暴露账号密码登个人关键信息

常见脱密方式:(推荐使用第二种方式)

1.xml配置对应的key然后对其value进行相应规则的脱敏(此种方式对于没有key的值无法脱敏并且系统重也不好穷尽所有需要脱敏的key)
2.采用正则表达式匹配对应的关键信息进行脱敏(满足正则表达是即可正确的脱敏相应的内容)

下面就对第二种方式详细说明:

1.定义一个SensitiveLogDataConverter

import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import cn.hutool.core.util.StrUtil;

import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SensitiveLogDataConverter extends MessageConverter {

    // 脱敏符号 *
    public static final String SYMBOL_STAR = "*";

    /**
     * 0 加密前三位
     * 1 加密中间四位
     * 2 加密后面四位
     */
    public static Integer logMode = 1;

    /**
     * 是否开启加密规则 默认为false
     */
    public static boolean logSensitive = false;
    /**
     * 手机号
     */
    private static final Pattern PHONE_PATTERN = Pattern.compile("(?<!\\d)(1\\d{10})(?!\\d)");

    private static final Pattern LANDLINE_AREA = Pattern.compile("[0][1-9]{2,3}-[0-9]{5,10}"); // 验证带区号的

    private static final Pattern LANDLINE = Pattern.compile("[1-9]{1}[0-9]{5,8}"); // 验证没有区号的

    /**
     * 证件号码
     */
    private static final Pattern ID_NUMBER = Pattern.compile("((1[1-5])|(2[1-3])|(3[1-7])|(4[1-6])|(5[0-4])|(6[1-5])|71|81|82|91)[0-9]{4}(18|19|20)[0-9]{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)[0-9]{3}[0-9Xx]");


    /**
     * 银行卡
     */
    //private static final Pattern BANK_CARD_NUMBER = Pattern.compile("^(?:62|88|99|19\\d{2}|4\\d{3}|5[1-5]\\d{2}|6\\d{3}|81\\d{2})\\d{10}|^62\\d{12,17}|^[0-9]{16,19}$");


    private static final Pattern BANK_CARD_NUMBER = Pattern.compile("[0-9]{16,19}");
    /**
     * 邮箱符号
     */
    private static final Pattern EMAIL = Pattern.compile("\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}");

    //private static final Pattern ADDRESS = Pattern.compile("/^([\\u4e00-\\u9fa5]+(?:省|市|自治区)){0,1}([\\u4e00-\\u9fa5]+(?:市|区|县|州|盟)){0,1}([\\u4e00-\\u9fa5]+(?:街道|镇|乡)){0,1}([\\u4e00-\\u9fa5]+(?:号|村|社)){0,1}([\\u4e00-\\u9fa5]+(?:路|街|巷)){0,1}([\\u4e00-\\u9fa5]+(?:弄|号楼|栋|单元)){0,1}([0-9A-Za-z]{5,}){0,1}$/");

    //
    private static final Pattern ADDRESS = Pattern.compile("([^区]+区)|([^镇]+镇)|([^路]+路)|([^街]+街)|([^单元]+单元)|([^公司]+公司)|([^室]+室)|([^户]+户)");


    @Override
    public String convert(ILoggingEvent event) {
        String logMsg = event.getFormattedMessage();
        if (logSensitive) {
            try {

                final Set<String> email = validateEmail(logMsg);
                if (!email.isEmpty()) {
                    for (String param : email) {
                        logMsg = logMsg.replaceAll(param, emailEncrypt(param));
                    }
                }

                final Set<String> phoneNumber = validatePhoneNumber(logMsg);
                //电话号码加密
                if (!phoneNumber.isEmpty()) {
                    for (String param : phoneNumber) {
                        logMsg = logMsg.replaceAll(param, mobileEncrypt(param));
                    }
                }

                final Set<String> landlineArea = validateLandlineArea(logMsg);
                if (!phoneNumber.isEmpty()) {
                    for (String param : landlineArea) {
                        logMsg = logMsg.replaceAll(param, idNumberEncrypt(param));
                    }
                }

                final Set<String> IDNumber = validateIDNumber(logMsg);
                if (!IDNumber.isEmpty()) {
                    for (String param : IDNumber) {
                        logMsg = logMsg.replaceAll(param, idNumberEncrypt(param));
                    }
                }
                final Set<String> cardNumber = validateCardNumber(logMsg);
                if (!cardNumber.isEmpty()) {
                    for (String param : cardNumber) {
                        logMsg = logMsg.replaceAll(param, idNumberEncrypt(param));
                    }
                }
                final Set<String> bankCardNumber = validateBankCardNumber(logMsg);
                if (!bankCardNumber.isEmpty()) {
                    for (String param : bankCardNumber) {
                        logMsg = logMsg.replaceAll(param, idNumberEncrypt(param));
                    }
                }

                logMsg = dealAddress(logMsg);
                return logMsg;
            } catch (Exception e) {
                e.printStackTrace();
                return super.convert(event);
            }
        } else {
            return logMsg;
        }
    }

    private static String dealAddress(String logMsg) {
        char[] charArray = logMsg.toCharArray();
        if (logMsg.contains("市")) {
            while (logMsg.contains("市")) {
                for (int i = logMsg.indexOf("市"); i < logMsg.length() - 1; i++) {
                    if (checkAddressEnd(charArray[i])) {
                        break;
                    }
                    charArray[i] = SYMBOL_STAR.charAt(0);
                }
                logMsg = String.valueOf(charArray);
            }
        } else {
            if (logMsg.contains("省")) {
                while (logMsg.contains("省")) {
                    for (int i = logMsg.indexOf("省"); i < logMsg.length() - 1; i++) {
                        if (checkAddressEnd(charArray[i])) {
                            break;
                        }
                        charArray[i] = SYMBOL_STAR.charAt(0);

                    }
                    logMsg = String.valueOf(charArray);
                }
            }
        }
        return String.valueOf(charArray);
    }

    private static boolean checkAddressEnd(char c) {
        return String.valueOf(c).equals(" ") || String.valueOf(c).equals(":") || String.valueOf(c).equals(";") ||
            String.valueOf(c).equals(":") || String.valueOf(c).equals(";") || String.valueOf(c).equals(",")
            || String.valueOf(c).equals(",") || String.valueOf(c).equals("“") || String.valueOf(c).equals("\"");
    }

    /**
     * 获取日志字符串内容中符合手机号
     */
    private static Set<String> validatePhoneNumber(String param) {
        Set<String> set = new HashSet<>();
        // 匹配手机号
        Matcher phoneMatcher = PHONE_PATTERN.matcher(param);
        while (phoneMatcher.find()) {
            set.add(phoneMatcher.group());
        }
        return set;
    }

    private static Set<String> validateLandline(String param) {
        Set<String> set = new HashSet<>();
        // 匹配手机号
        Matcher phoneMatcher = LANDLINE.matcher(param);
        while (phoneMatcher.find()) {
            set.add(phoneMatcher.group());
        }
        return set;
    }


    private static Set<String> validateLandlineArea(String param) {
        Set<String> set = new HashSet<>();
        // 匹配手机号
        Matcher phoneMatcher = LANDLINE_AREA.matcher(param);
        while (phoneMatcher.find()) {
            set.add(phoneMatcher.group());
        }
        return set;
    }


    private static Set<String> validateIDNumber(String param) {
        Set<String> set = new HashSet<>();
        Matcher phoneMatcher = ID_NUMBER.matcher(param);
        while (phoneMatcher.find()) {
            set.add(phoneMatcher.group());
        }
        return set;
    }

    private static Set<String> validateCardNumber(String param) {
        Set<String> set = new HashSet<>();
        Matcher phoneMatcher = CARD_NUMBER.matcher(param);
        while (phoneMatcher.find()) {
            set.add(phoneMatcher.group());
        }
        return set;
    }

    private static Set<String> validateBankCardNumber(String param) {
        Set<String> set = new HashSet<>();
        Matcher phoneMatcher = BANK_CARD_NUMBER.matcher(param);
        while (phoneMatcher.find()) {
            set.add(phoneMatcher.group());
        }
        return set;
    }

    private static Set<String> validateEmail(String param) {
        Set<String> set = new HashSet<>();
        Matcher phoneMatcher = EMAIL.matcher(param);
        while (phoneMatcher.find()) {
            set.add(phoneMatcher.group());
        }
        return set;
    }

    private static Set<String> validateAddress(String param) {
        Set<String> set = new HashSet<>();
        Matcher phoneMatcher = ADDRESS.matcher(param);
        while (phoneMatcher.find()) {
            set.add(phoneMatcher.group());
        }
        return set;
    }

    /**
     * 手机号脱敏
     *
     * @param mobile
     * @return
     */
    public static String mobileEncrypt(String mobile) {
        if (StrUtil.isEmpty(mobile) || (mobile.length() != 11)) {
            return mobile;
        }
        switch (logMode) {
            case 0:
                return mobile.replaceAll("(\\d{3})(\\d{4})(\\d{4})", "***$2$3");
            case 1:
                return mobile.replaceAll("(\\d{3})(\\d{4})(\\d{4})", "$1****$3");
            case 2:
                return mobile.replaceAll("(\\d{3})(\\d{4})(\\d{4})", "$1$2****");
            default:
                return mobile;
        }
    }

    public static String idNumberEncrypt(String idNumber) {
        if (StrUtil.isEmpty(idNumber)) {
            return idNumber;
        }
        return filterString(idNumber, SYMBOL_STAR);
    }

    public static String emailEncrypt(String email) {
        if (StrUtil.isEmpty(email)) {
            return email;
        }
        return filterEmail(email, SYMBOL_STAR);
    }

    public static String addressEncrypt(String address) {
        if (StrUtil.isEmpty(address)) {
            return address;
        }
        return filterAddress(address, SYMBOL_STAR);
    }


    public static String filterAddress(String address, String symbol) {
        if (StrUtil.isNotBlank(address) && !address.contains("省") && !address.contains("市")) {
            return buildSymbol(symbol, address.length());
        }
        return address;
    }

    public static String filterEmail(String email, String symbol) {
        if (email.contains("@")) {
            int i = email.lastIndexOf("@");
            String front = email.substring(0, i);
            String rear = email.substring(i).replace("@", "#");
            return filterString(front, symbol).concat(rear);
        }
        return email;
    }

    public static String filterString(String s, String symbol) {
        if (StrUtil.isNotBlank(s)) {
            char[] charArray = s.toCharArray();
            switch (logMode) {
                //隐藏前三位
                case 0:
                    for (int i = 0; i < Math.min(3, charArray.length); i++) {
                        charArray[i] = symbol.charAt(0);
                    }
                    return String.valueOf(charArray);
                //隐藏中间4位
                case 1:
                    for (int i = Math.max((charArray.length / 2 - 2), 0); i < (Math.min((charArray.length / 2 + 2), charArray.length)); i++) {
                        charArray[i] = symbol.charAt(0);
                    }
                    return String.valueOf(charArray);
                case 2:
                    for (int i = Math.max((charArray.length - 1), 0); i >= (Math.max((charArray.length - 4), 0)); i--) {
                        charArray[i] = symbol.charAt(0);
                    }
                    return String.valueOf(charArray);
                default:
                    break;
            }
        }
        return s;
    }


    /**
     * @param symbol 符号
     * @param num    数量
     * @return 创建一定数量的相同符号字符串
     */
    public static String buildSymbol(String symbol, int num) {
        if (num < 0) {
            throw new IndexOutOfBoundsException("-1");
        }
        StringBuilder value = new StringBuilder();
        while (--num >= 0) {
            value.append(symbol);
        }
        return value.toString();
    }
}

备注: logMode 与 logSensitive 参数 可配置在启动参数上,然后springboot项目启动时读取入参并赋值给对应的静态变量以达到日志规则与日志开关的动态控制。
启动参数配置
在这里插入图片描述

2.xml中配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <property name="APP_NAME" value="data"/>
    <property name="LOG_HOME" value="./data/logs/${APP_NAME}"/>

    <conversionRule conversionWord="m" converterClass="....SensitiveLogDataConverter"/>
    ......
</configuration>

补充:正则表达式不会的小伙伴可以去网上找,案例很多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值