logback 日志脱敏处理

  1.按正则表达式脱敏处理

  参考:

    https://www.cnblogs.com/htyj/p/12095615.html

    http://www.heartthinkdo.com/?p=998

  站在两位创作者的肩膀上,我很不要脸的将他们的内容做了下整合,捂脸中...

  一般处理都是继承PatternLayout实现自己的处理方式,上代码

  注意:这里隐藏处理只是针对数字类型的字符串做了简单的编码替换处理,可用其他通用加密方式进行替代。

package com.demo.log;


import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;

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

/**
 * 对敏感信息进行掩盖。
 * 1.实现原理
 * 对产生的日志信息,进行正则匹配和替换。
 * <p>
 * 2.目前包括如下类型的信息:银行卡号、电话、身份证和邮箱。
 * <p>
 * 3.如何进行扩展新的正则类型
 * (1)在PatternType枚举中新增一个正则
 * (2)extractMatchesByType对新增的正则做处理
 * (3)maskByType对新增的正则做处理
 * <p>
 */
public class MaskingPatternLayout extends PatternLayout {

    /**
     * 匹配的所有正则
     */
    private Map<PatternType, Pattern> patternsMap = new HashMap<>();
    private static final String KEY = "GepqwLZYdk";


    public MaskingPatternLayout() {
        loadPatterns();
    }


    @Override
    public String doLayout(ILoggingEvent event) {
        String message = super.doLayout(event);
        if (CollectionUtils.isEmpty(patternsMap)) {
            return message;
        }
        // 处理日志信息
        try {
            return process(message);
        } catch (Exception e) {
            // 这里不做任何操作,直接返回原来message
            return message;
        }
    }


    /**
     * 加载正则表达式,生成相应的Pattern对象。
     */
    private void loadPatterns() {
        for (PatternType patternType : PatternType.values()) {
            Pattern pattern = Pattern.compile(patternType.getRegex());
            patternsMap.put(patternType, pattern);
        }
    }

    /**
     * 替换信息
     *
     * @param message
     * @return
     */
    public String process(String message) {
        for (PatternType key : patternsMap.keySet()) {
            // 1.生成matcher
            Pattern pattern = patternsMap.get(key);
            Matcher matcher = pattern.matcher(message);

            // 2.获取匹配的信息
            Set<String> matches = extractMatchesByType(matcher);

            // 3.掩盖匹配的信息
            if (!CollectionUtils.isEmpty(matches)) {
                message = maskByType(key, message, matches);
            }
        }

        return message;
    }


    /**
     * 根据正则类型来做相应的提取
     *
     * @param matcher
     * @return
     */
    private Set<String> extractMatchesByType(Matcher matcher) {
        // 邮箱、电话、银行卡、身份证都是通过如下方法进行提取匹配的字符串
        return extractDefault(matcher);

    }

    /**
     * 1.提取匹配的所有字符串中某一个分组
     * group(0):表示不分组,整个表达式的值
     * group(i),i>0:表示某一个分组的值
     * <p>
     * 2.使用Set进行去重
     *
     * @param matcher
     * @return
     */
    private Set<String> extractDefault(Matcher matcher) {
        Set<String> matches = new HashSet<>();
        int count = matcher.groupCount();

        while (matcher.find()) {
            if (count == 0) {
                matches.add(matcher.group());
                continue;
            }
            for (int i = 1; i <= count; i++) {
                String match = matcher.group(i);
                if (null != match) {
                    matches.add(match);
                }
            }

        }

        return matches;
    }


    /**
     * 根据不同类型敏感信息做相应的处理
     *
     * @param key
     * @param message
     * @return
     */
    private String maskByType(PatternType key, String message, Set<String> matchs) {
        if (key == PatternType.ID_CARD) {
            return maskIdCard(message, matchs);
        } else if(key == PatternType.BANK_CARD){
            return maskBankcard(message, matchs);
        } else if(key == PatternType.PHONE_NUMBER){
            return maskPhone(message, matchs);
        }  else{
            return message;
        }


    }

    /**
     * 掩盖数字类型信息
     *
     * @param message
     * @param matches
     * @return
     */
    private String maskIdCard(String message, Set<String> matches) {

        for (String match : matches) {
            // 1.处理获取的字符
            String matchProcess = baseSensitive(match, 4, 4);
            // 2.String的替换
            message = message.replace(match, matchProcess);
        }
        return message;
    }

    private String maskBankcard(String message, Set<String> matches) {
        for (String match : matches) {
            // 1.处理获取的字符
            String matchProcess = baseSensitive(match, 3, 3);
            // 2.String的替换
            message = message.replace(match, matchProcess);
        }
        return message;
    }

    private String maskPhone(String message, Set<String> matches) {
        for (String match : matches) {
            // 1.处理获取的字符
            String matchProcess = baseSensitive(match, 2, 2);
            // 2.String的替换
            message = message.replace(match, matchProcess);
        }
        return message;
    }

    private static String baseSensitive(String str, int startLength, int endLength) {
        if (StringUtils.isBlank(str)) {
            return "";
        }
        String replacement = str.substring(startLength,str.length()-endLength);
        StringBuffer sb = new StringBuffer();
        for(int i=0;i<replacement.length();i++) {
            char ch;
            if(replacement.charAt(i)>='0' && replacement.charAt(i)<='9') {
                ch = KEY.charAt((int)(replacement.charAt(i) - '0'));
            }else {
                ch = replacement.charAt(i);
            }
            sb.append(ch);
        }
        return StringUtils.left(str, startLength).concat(StringUtils.leftPad(StringUtils.right(str, endLength), str.length() - startLength, sb.toString()));
    }

    private static String decrypt(String str, int startLength, int endLength) {
        if (StringUtils.isBlank(str)) {
            return "";
        }
        String replacement = str.substring(startLength,str.length()-endLength);
        StringBuffer sb = new StringBuffer();
        for(int i=0;i<replacement.length();i++) {
            int index = KEY.indexOf(replacement.charAt(i));
            if(index != -1) {
                sb.append(index);
            }else {
                sb.append(replacement.charAt(i));
            }
        }
        return StringUtils.left(str, startLength).concat(StringUtils.leftPad(StringUtils.right(str, endLength), str.length() - startLength, sb.toString()));
    }


    /**
     * 定义敏感信息类型
     */
    private enum PatternType {
        // 1.手机号共11位,模式为: 13xxx,,14xxx,15xxx,17xxx,18xx
        PHONE_NUMBER("手机号", "[^\\d](1[34578]\\d{9})[^\\d]"),
        // 2.银行卡号,包含16位和19位
        BANK_CARD("银行卡", "[^\\d](\\d{16})[^\\d]|[^\\d](\\d{19})[^\\d]"),
        // 3.邮箱
        EMAIL("邮箱", "[A-Za-z_0-9]{1,64}@[A-Za-z1-9_-]+.[A-Za-z]{2,10}"),
        // 4. 15位(全为数字位)或者18位身份证(17位位数字位,最后一位位校验位)
        ID_CARD("身份证", "[^\\d](\\d{15})[^\\d]|[^\\d](\\d{18})[^\\d]|[^\\d](\\d{17}X)");

        private String description;
        private String regex;

        private PatternType(String description, String regex) {
            this.description = description;
            this.regex = regex;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public String getRegex() {
            return regex;
        }

        public void setRegex(String regex) {
            this.regex = regex;
        }
    }

}

  logback.xml:

    <property name="rolling.pattern" value="%d{yyyy-MM-dd}"/>
    <property name="layout.pattern" value="%-5p %d [%t] %c{50} > %m%n"/>

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="com.demo.log.MaskingPatternLayout">
                <pattern>${layout.pattern}</pattern>
            </layout>
        </encoder>
    </appender>

  2.按指定字段脱敏处理

  参考:https://gitee.com/cqdevops/diary_desensitization

  注意:这种方式是需要一定前提条件的,日志内容的格式有限制(如json串或者{字段名=“”}),具体可以到参考文章看看,然后可以在源码的基础上自己调整。

       说明一下,这里是指cardId跟idNo这两者的字段名的内容按idCardNo类型处理,realName字段名的内容按照trueName方式处理,一开始我也看得云里雾里。 

      

    

     下载源码后,导入工程后,maven install到本地仓库,不能直接使用install后的jar,因为它没有把依赖包打进去,引用的话会报ClassNotFound

      在你maven工程下的pom.xml引用,文章中引用的groupId是错误的,所以会一直引不到:

       <dependency>
            <groupId>com.gitee.cqdevops</groupId>
            <artifactId>desensitization-logback</artifactId>
            <version>1.1.1</version>
        </dependency>    

     针对源码做了一些微调,对字段内容开始的tag做了一下处理,但可能不是最优的处理:

package com.gitee.cqdevops.desensitization.pattern;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class KeywordConverter extends BaseConverter {

    private static Pattern pattern = Pattern.compile("[0-9a-zA-Z]");

    @Override
    public String invokeMsg(final String oriMsg){
        String tempMsg = oriMsg;
        try {
            if("true".equals(converterCanRun)){
                if(!keywordMap.isEmpty()){
                    Set<String> keysArray = keywordMap.keySet();
                    for(String key: keysArray){
                        int index = -1;
                        int i = 0;
                        do{
                            index = tempMsg.indexOf(key, index + 1);
                            if(index != -1){
                                if(isWordChar(tempMsg, key, index)){
                                    continue;
                                }
                                Map<String,Object> valueStartMap = getValueStartIndex(tempMsg, index + key.length());

                                int valueStart = (int)valueStartMap.get("valueStart");
                                char tag = (char)valueStartMap.get("tag");
                                int valueEnd = getValueEndEIndex(tempMsg, valueStart,tag);
                                // 对获取的值进行脱敏
                                String subStr = tempMsg.substring(valueStart, valueEnd);
                                subStr = facade(subStr, keywordMap.get(key));
                                tempMsg = tempMsg.substring(0,valueStart) + subStr + tempMsg.substring(valueEnd);
                                i++;
                            }
                        }while(index != -1 && i < depth);
                    }
                }
            }
        } catch (Exception e) {
            return tempMsg;
        }
        return tempMsg;
    }

    /**
     * 判断key是否为单词内字符
     * @param msg 待检查字符串
     * @param key 关键字
     * @param index 起始位置
     * @return 判断结果
     */
    private boolean isWordChar(String msg, String key, int index){
        if(index != 0){
            // 判断key前面一个字符
            char preCh = msg.charAt(index-1);
            Matcher match = pattern.matcher(preCh + "");
            if(match.matches()){
                return true;
            }
        }
        // 判断key后面一个字符
        char nextCh = msg.charAt(index + key.length());
        Matcher match = pattern.matcher(nextCh + "");
        if(match.matches()){
            return true;
        }
        return false;
    }


    private Map<String,Object> getValueStartIndex(String msg, int valueStart ){
        Map<String,Object> map= new HashMap<>();
        do{
            char ch = msg.charAt(valueStart);
            if(ch == ':' || ch == '='){
                valueStart ++;
                ch = msg.charAt(valueStart);
                if(ch == '"' || ch =='\''){
                    valueStart ++;
                    map.put("valueStart",valueStart);
                    map.put("tag",ch);
                }
                break;
            }else{
                valueStart ++;
            }
        }while(true);

        return map;
    }

    private int getValueEndEIndex(String msg, int valueEnd,char tag){
        do{
            if(valueEnd == msg.length()){
                break;
            }
            char ch = msg.charAt(valueEnd);

            if(ch == tag){
                if(valueEnd + 1 == msg.length()){
                    break;
                }
                char nextCh = msg.charAt(valueEnd + 1);
                if(nextCh == ';' || nextCh == ','|| nextCh == '}'){
                    while(valueEnd > 0 ){
                        char preCh = msg.charAt(valueEnd - 1);
                        if(preCh != '\\'){
                            break;
                        }
                        valueEnd--;
                    }
                    break;
                }else{
                    valueEnd ++;
                }
            } else{
                valueEnd ++;
            }
        }while(true);

        return valueEnd;
    }

    /**
     * 寻找key对应值的开始位置
     * @param msg 待检查字符串
     * @param valueStart 开始寻找位置
     * @return key对应值的开始位置
     */
//    private int getValueStartIndex(String msg, int valueStart ){
//        do{
//            char ch = msg.charAt(valueStart);
//            if(ch == ':' || ch == '='){
//                valueStart ++;
//                ch = msg.charAt(valueStart);
//                if(ch == '"'){
//                    valueStart ++;
//                }
//                break;
//            }else{
//                valueStart ++;
//            }
//        }while(true);
//
//        return valueStart;
//    }

    /**
     * 寻找key对应值的结束位置
     * @param msg 待检查字符串
     * @param valueEnd 开始寻找位置
     * @return key对应值的结束位置
     */
    private int getValueEndEIndex(String msg, int valueEnd){
        do{
            if(valueEnd == msg.length()){
                break;
            }
            char ch = msg.charAt(valueEnd);

            if(ch == '"'){
                if(valueEnd + 1 == msg.length()){
                    break;
                }
                char nextCh = msg.charAt(valueEnd + 1);
                if(nextCh == ';' || nextCh == ','|| nextCh == '}'){
                    while(valueEnd > 0 ){
                        char preCh = msg.charAt(valueEnd - 1);
                        if(preCh != '\\'){
                            break;
                        }
                        valueEnd--;
                    }
                    break;
                }else{
                    valueEnd ++;
                }
            }else if (ch ==';' || ch == ',' || ch == '}'){
                break;
            }else{
                valueEnd ++;
            }
        }while(true);

        return valueEnd;
    }
}

    

 

 

  

  

  

  

posted on 2020-12-07 17:09  zgz2016  阅读(3704)  评论(0编辑  收藏  举报

导航