日志脱敏功能

前言

数据安全尤为重要,最为简单的防线就是防止重要信息(身份证、手机号、姓名等)明文显示,对此需要在数据库层、日志层等做好数据加解密。

思路

1、编写需加密的正则模板、加密字段
2、重写ch.qos.logback.classic.pattern.MessageConverter
3、编写日志框架,确认转换模板(本篇采用logback.xml)

看不明白的直接无脑复制代码,在logback-sensitive-rule.properties文件中加上你需要的脱敏的字段即可

实战

正则模板及加密字段

在resource目录下建立两个properties文件,logback-sensitive-def-rule 和 logback-sensitive-rule。前者logback-sensitive-def-rule 主要指定正则表达式和默认加密字段,可作为公共二方包供所有项目使用,logback-sensitive-rule主要用于定制化项目,对特别项目需要加密字段的做定制化。

logback-sensitive-def-rule

#mobile  \"mobile\"|\"telphone\"
# ??json
def_rule1=(KEYWORD)(\\s{0,})(:)(\\s{0,})(\\\\"|\")(\\w{3})(\\w{4})(\\w{4})*(\\\\"|\")&$1$2$3$4$5$6****$8$9&mobile
# ??toString
def_rule_sec11=(KEYWORD)(\\s{0,})(:|=)(\\s{0,})(\"|\'|)(\\w{3})(\\w{4})(\\w{4})*(\"|\'|)&$1$2$3$4$5$6****$8$9&mobile
def_rule_sec12=(KEYWORD)(\\\\{0,}\\\"\\s{0,})(:)(\\s{0,})(\\\\{0,}\\\")(\\w{3})(\\w{4})(\\w{4})*(\\\\{0,}\\\")&$1$2$3$4$5$6****$8$9&mobile
def_field_rule_mobile=mobile|telphone

#idcard,bankcard
def_rule2=(KEYWORD)(\\s{0,})(:)(\\s{0,})(\\\\"|\")(\\w{3})(\\w{1,})(\\w{4})(\\\\"|\")&$1$2$3$4$5$6*********$8$9&idcard
def_rule_sec21=(KEYWORD)(\\s{0,})(:|=)(\\s{0,})(\"|\'|)(\\w{3})(\\w{1,})(\\w{4})(\"|\'|)&$1$2$3$4$5$6*********$8$9&idcard
def_rule_sec22=(KEYWORD)(\\\\{0,}\\\"\\s{0,})(:)(\\\\{0,}\\s{0,})(\")(\\w{3})(\\w{1,})(\\w{4})(\\\\{0,}\\s{0,})(\")&$1$2$3$4$5$6******$8$9$10&idcard
def_field_rule_idcard=idcard|bankcard|idNo

#pwd ("pwd")(\s{0,})(:|=)(\s{0,})(\\"|")(.*?)(\\"|")
def_rule3=(KEYWORD)(\\s{0,})(:)(\\s{0,})(\\\\"|\")(.*?)(\\\\"|\")&$1$2$3$4$5*****$7&pwd
def_rule_sec31=(KEYWORD)(\\s{0,})(:|=)(\\s{0,})(\"|)(.*?)(\"|,|\\)|])&$1$2$3$4$5*****$7&pwd
# def_rule_sec32=(KEYWORD)(,)(.*?)&$1$2*****&pwd
def_field_rule_pwd=password|pwd

#username
# ??json
def_rule4=(KEYWORD)(\\s{0,})(:)(\\s{0,})(\\\\"|\")([\u4E00-\u9FA5]{1})[\u4E00-\u9FA5]{1,}(\\\\"|\")&$1$2$3$4$5$6**$7&name
# ??toString
def_rule_sec41=(KEYWORD)(\\s{0,})(:|=)(\\s{0,})(\"|\'|)([\u4E00-\u9FA5]{1})([\u4E00-\u9FA5]{1,})(\"|\'|)&$1$2$3$4$5$6**$8&name
# ????json???
def_rule_sec42=(KEYWORD)(\\\\{0,}\\\"\\s{0,})(:)(\\\\{0,}\\s{0,})(\\\")([\u4E00-\u9FA5]{1})([\u4E00-\u9FA5]{1,})(\\\\{0,}\\\")&$1$2$3$4$5$6**$8&name
def_field_rule_name=customerName|userName|name|custName

#email
def_rule5=(KEYWORD)(\\s{0,})(:)(\\s{0,})(\\\\"|\")([A-Za-z0-9\u4e00-\u9fa5|_-]{1,2})([A-Za-z0-9\u4e00-\u9fa5|_-]{1,})@([a-z0-9A-Z_-]{2,})+(\\.[a-zA-Z0-9_-]{1,})(\\\\"|\")&$1$2$3$4$5$6***@$8$9$10&email
def_rule_sec51=(KEYWORD)(\\s{0,})(:|=)(\\s{0,})(\"|\'|)([A-Za-z0-9\u4e00-\u9fa5|_-]{1,2})([A-Za-z0-9\u4e00-\u9fa5|_-]{1,})@([a-z0-9A-Z_-]{2,})+(\\.[a-zA-Z0-9_-]{1,})(\"|\'|)&$1$2$3$4$5$6***@$8$9&email
def_field_rule_email=email

#address
def_rule6=
# 未知长度,且未知数据格式
def_rule_sec61=(KEYWORD)(\\s{0,})(:|=)(\\s{0,})(\"|\')([\\u4E00-\\u9FA5]{0,}?\\S{0,}?)(\\S{0,10})(\"|\')&$1$2$3$4$5$6*****$8&address
def_rule_sec62=(KEYWORD)(\\\\{0,}\\\"\\s{0,})(:)(\\\\{0,}\\s{0,})(\\\")([\\u4E00-\\u9FA5]{0,}?\\S{0,}?)(\\S{0,10})(\\\\{0,}\\\")&$1$2$3$4$5$6*****$8&address
def_field_rule_address=address|idAddress

logback-sensitive-rule

# 定制化字段 分别代表 电话、姓名、证件号、地址
sen_field_rule_mobile=mobile|custMobileNo|bankPhone|mobileNo|reserveMobileNo|phone|contactPhone|accountPhone|custMobile|bankMobile
sen_field_rule_name=custName|acctName|openCustName|OCRname|name|accountName|dbAcctName|bankAcctName
sen_field_rule_idcard=idNo|acct|bankCardNo|ocrPrcid|certNo|dbAcct|id|originValue|idCardNo
sen_field_rule_address=custAddress|address|idAddress|entAddress|certAddress|ocrIdAddress
LogBackSensitiveConverter

重新ch.qos.logback.classic.pattern.MessageConverter的convert方法,主要实现思路是约定号规则字段(如sen_field_rule_、def_field_rule_),从properties文件中读取配置。

LogBackSensitiveConverter


import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;

/**
 * 敏感信息脱敏处理
 *
 */
public class LogBackSensitiveConverter extends MessageConverter {
    private static Logger log = LoggerFactory.getLogger(LogBackSensitiveConverter.class);
    private static final String SEN_RULE = "sen_rule_";
    private static final String SEN_RULE_FIELD = "sen_field_rule_";
    private static final String DEFAULT_RULE = "def_rule";
    private static final String DEFAULT_RULE_FIELD = "def_field_rule_";

    /** 脱敏规则 */
    private static volatile List<RuleConfig> configList;
    /** 是否脱敏开关 */
    private static volatile boolean sensitiveOpen;
    /** 是否初始化配置 */
    private static volatile boolean initFlag;

    @Override
    public String convert(ILoggingEvent event) {
        init();
        return doConvert(event);
    }

    private void init() {
        try {
            if (!initFlag) {
                initFlag = true;

                Map<String, String> propertyMap = ((LoggerContext) LoggerFactory.getILoggerFactory()).getCopyOfPropertyMap();
                sensitiveOpen = Boolean.valueOf(propertyMap.get("sensitiveOpen"));

                if (sensitiveOpen) {
                    initRuleConfig(propertyMap);
                }
            }
        } catch (Exception e) {
            configList = null;
            log.error("error,{}", e);
        }
    }

    private String doConvert(ILoggingEvent event) {
        try {
            //本工具打日志不做转换
            if (LogBackSensitiveConverter.class.getName().equals(event.getLoggerName())) {
                return event.getFormattedMessage();
            }
            String result = event.getFormattedMessage();
            if (configList != null && configList.size() > 0) {
                for (RuleConfig ruleConfig : configList) {
                    result = ruleConfig.apply(result);
                }
                if (StringUtils.isEmpty(result)) {
                    result = event.getFormattedMessage();
                }
            } else {
                result = super.convert(event);
            }
            return result;
        } catch (Exception e) {
            log.error("error,{}", e);
            return event.getFormattedMessage();
        }
    }

    private void initRuleConfig(Map<String, String> propertyMap) {
        if (configList == null) {
            synchronized (LogBackSensitiveConverter.class) {
                if (configList == null) {

                    // read properties
                    PropertiesUtil propertiesUtil = new PropertiesUtil("logback-sensitive-def-rule.properties");
                    Properties properties = propertiesUtil.get();
                    configList = new ArrayList<>();

                    //添加默认表达式
                    addDefaultRule(propertyMap, properties);
                    //添加自定义正则表达式
                    addCustomRule(propertyMap);

//                    this.addInfo("logback sensitive rule init end ! ");
                    log.info("logback_sensitive_rule_init_end !");
                }
            }
        }
    }

    private void addCustomRule(Map<String, String> propertyMap) {
        for (String s : propertyMap.keySet()) {
            if (s.startsWith(SEN_RULE)) {
                String[] array = propertyMap.get(s).split("&");
                if (ArrayUtils.isNotEmpty(array) && array.length == 2) {
                    configList.add(new RuleConfig(array[0], array[1]));
                }
            }
        }
    }

    private void addDefaultRule(Map<String, String> propertyMap, Properties properties) {
        for (String key : properties.stringPropertyNames()) {
            if (!key.startsWith(DEFAULT_RULE)) {
                continue;
            }

            String[] array = properties.getProperty(key).split("&");
            if (ArrayUtils.isNotEmpty(array) && array.length == 3) {

                //读默认字段
                String defFields = String.valueOf(properties.get(DEFAULT_RULE_FIELD + array[2]));
                //读自定义字段
                String senFields = propertyMap.get(SEN_RULE_FIELD + array[2]);
                String keyword = StringUtils.EMPTY;
                if (key.indexOf("sec") != -1) {
                    senFields = StringUtils.isEmpty(senFields) ? "" : "|" + senFields;
                    keyword = defFields + senFields;
                } else {
                    keyword = "\"" + defFields.replace("|", "\"|\"") + "\"";
                    if (!StringUtils.isEmpty(senFields)) {
                        keyword += "|" + "\"" + senFields.replace("|", "\"|\"") + "\"";
                    }

                    keyword += "|\\\\\"" + defFields.replace("|", "\\\\\"|\\\\\"") + "\\\\\"";
                    if (!StringUtils.isEmpty(senFields)) {
                        keyword += "|\\\\\"" + senFields.replace("|", "\\\\\"|\\\\\"") + "\\\\\"";
                    }
                }
                if (StringUtils.isEmpty(keyword)) {
                    continue;
                }
                configList.add(new RuleConfig(array[0].replace("KEYWORD", keyword), array[1]));
            }
        }
    }


    private class RuleConfig {
        private String reg;
        private String replacement;
        private Pattern pattern;

        RuleConfig(String reg, String replacement) {
            this.reg = reg;
            this.replacement = replacement;
            this.pattern = Pattern.compile(reg);
        }

        public String getReg() {
            return reg;
        }

        public void setReg(String reg) {
            this.reg = reg;
        }

        public String getReplacement() {
            return replacement;
        }

        public void setReplacement(String replacement) {
            this.replacement = replacement;
        }

        public Pattern getPattern() {
            return pattern;
        }

        public void setPattern(Pattern pattern) {
            this.pattern = pattern;
        }

        String apply(String message) {
            if (StringUtils.isEmpty(message)) {
                return message;
            }
            return this.pattern.matcher(message).replaceAll(replacement);
        }
    }
}

PropertiesUtil


import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;

/**
 * Created by rayfoo@qq.com Luna on 2020/4/15 18:38
 * Description : 读取配置文件工具类
 */
public class PropertiesUtil {
    private static Logger log = LoggerFactory.getLogger(PropertiesUtil.class);
    private Properties properties;

    public PropertiesUtil(String fileName) {
        if (StringUtils.isBlank(fileName)) {
            return;
        }
        properties = new Properties();
        try {
            properties.load(new InputStreamReader(PropertiesUtil.class
                    .getClassLoader()
                    .getResourceAsStream(fileName)));
        } catch (IOException e) {
            log.error("PropertiesUtil load data error :{}", e);
            return;
        }
    }

    public Properties get() {
        return this.properties;
    }

    public String getProperty(String key) {
        String value = properties.getProperty(key.trim());
        if (StringUtils.isBlank(value)) {
            return null;
        }
        return value.trim();
    }

    public String getProperty(String key, String defaultValue) {
        String value = properties.getProperty(key);
        if (StringUtils.isBlank(value)) {
            return defaultValue;
        }
        return value;
    }


}
配置logback,xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <!--     脱敏开启开关 必须配置-->
    <property scope="context" name="sensitiveOpen" value="true"/>
    <!--    自定义属性配置文件:可自定义字段名和匹配正则表达式  可选配置-->
    <property scope="context" resource="logback-sensitive-rule.properties"/>
    <!--    转换配置 必选配置-->
    <conversionRule conversionWord="msg" converterClass="com.xxx.xxx.xx.LogBackSensitiveConverter"> </conversionRule>
</configuration>
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值