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