1. 程式人生 > 實用技巧 >logback 日誌脫敏處理

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