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;
    }
}

    

 

 

  

  

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM