├── repo └── pers │ └── liuchengyin │ └── logback-desensitization │ ├── maven-metadata.xml.md5 │ ├── maven-metadata.xml.sha1 │ ├── 1.0.0 │ ├── logback-desensitization-1.0.0.jar.md5 │ ├── logback-desensitization-1.0.0.pom.md5 │ ├── logback-desensitization-1.0.0.jar.sha1 │ ├── logback-desensitization-1.0.0.pom.sha1 │ ├── logback-desensitization-1.0.0.jar │ └── logback-desensitization-1.0.0.pom │ └── maven-metadata.xml ├── src └── main │ └── java │ └── pers │ └── liuchengyin │ ├── logbackadvice │ ├── LcyFileAppender.java │ ├── LcyConsoleAppender.java │ ├── LcyRollingFileAppender.java │ └── DesensitizationAppender.java │ └── utils │ ├── YmlUtils.java │ └── DesensitizationUtil.java ├── pom.xml └── README.md /repo/pers/liuchengyin/logback-desensitization/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | c4f1dbf06482f701613e72ac0d756428 -------------------------------------------------------------------------------- /repo/pers/liuchengyin/logback-desensitization/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | 7c3453696d360b2bd471ef2521f4e8091baf7d94 -------------------------------------------------------------------------------- /repo/pers/liuchengyin/logback-desensitization/1.0.0/logback-desensitization-1.0.0.jar.md5: -------------------------------------------------------------------------------- 1 | ee1287693e97f8ea34570673109bff1a -------------------------------------------------------------------------------- /repo/pers/liuchengyin/logback-desensitization/1.0.0/logback-desensitization-1.0.0.pom.md5: -------------------------------------------------------------------------------- 1 | 20d17e856c8f8d77c845d258361d8af3 -------------------------------------------------------------------------------- /repo/pers/liuchengyin/logback-desensitization/1.0.0/logback-desensitization-1.0.0.jar.sha1: -------------------------------------------------------------------------------- 1 | c3d251a90de81114179cb01abcff31be8ad1a13e -------------------------------------------------------------------------------- /repo/pers/liuchengyin/logback-desensitization/1.0.0/logback-desensitization-1.0.0.pom.sha1: -------------------------------------------------------------------------------- 1 | c36f6577323d671ebf4199e340e165caea5bb88b -------------------------------------------------------------------------------- /repo/pers/liuchengyin/logback-desensitization/1.0.0/logback-desensitization-1.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengyin01/LogbackDesensitization/HEAD/repo/pers/liuchengyin/logback-desensitization/1.0.0/logback-desensitization-1.0.0.jar -------------------------------------------------------------------------------- /repo/pers/liuchengyin/logback-desensitization/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | pers.liuchengyin 4 | logback-desensitization 5 | 6 | 1.0.0 7 | 8 | 1.0.0 9 | 10 | 20210128024303 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/pers/liuchengyin/logbackadvice/LcyFileAppender.java: -------------------------------------------------------------------------------- 1 | package pers.liuchengyin.logbackadvice; 2 | 3 | import ch.qos.logback.classic.spi.LoggingEvent; 4 | import ch.qos.logback.core.FileAppender; 5 | 6 | /** 7 | * @ClassName FileAppenderDS 8 | * @Description 9 | * @Author 柳成荫 10 | * @Date 2021/1/9 11 | */ 12 | public class LcyFileAppender extends FileAppender { 13 | 14 | @Override 15 | protected void subAppend(Object event) { 16 | DesensitizationAppender appender = new DesensitizationAppender(); 17 | appender.operation((LoggingEvent) event); 18 | super.subAppend(event); 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/pers/liuchengyin/logbackadvice/LcyConsoleAppender.java: -------------------------------------------------------------------------------- 1 | package pers.liuchengyin.logbackadvice; 2 | 3 | import ch.qos.logback.classic.spi.LoggingEvent; 4 | import ch.qos.logback.core.ConsoleAppender; 5 | 6 | /** 7 | * @ClassName ConsoleAppenderDS 8 | * @Description 9 | * @Author 柳成荫 10 | * @Date 2021/1/9 11 | */ 12 | public class LcyConsoleAppender extends ConsoleAppender { 13 | 14 | @Override 15 | protected void subAppend(Object event) { 16 | DesensitizationAppender appender = new DesensitizationAppender(); 17 | appender.operation((LoggingEvent)event); 18 | super.subAppend(event); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/pers/liuchengyin/logbackadvice/LcyRollingFileAppender.java: -------------------------------------------------------------------------------- 1 | package pers.liuchengyin.logbackadvice; 2 | 3 | import ch.qos.logback.classic.spi.LoggingEvent; 4 | import ch.qos.logback.core.rolling.RollingFileAppender; 5 | 6 | /** 7 | * @ClassName RollingFileAppenderDS 8 | * @Description 9 | * @Author 柳成荫 10 | * @Date 2021/1/9 11 | */ 12 | public class LcyRollingFileAppender extends RollingFileAppender { 13 | 14 | @Override 15 | protected void subAppend(Object event) { 16 | DesensitizationAppender appender = new DesensitizationAppender(); 17 | appender.operation((LoggingEvent)event); 18 | super.subAppend(event); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.1 9 | 10 | 11 | pers.liuchengyin 12 | logback-desensitization 13 | 1.0.0 14 | LogbackDesensitization 15 | Logback Desensitization 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter 25 | 26 | 27 | com.alibaba 28 | fastjson 29 | 1.2.8 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/pers/liuchengyin/logbackadvice/DesensitizationAppender.java: -------------------------------------------------------------------------------- 1 | package pers.liuchengyin.logbackadvice; 2 | 3 | import ch.qos.logback.classic.spi.LoggingEvent; 4 | import pers.liuchengyin.utils.DesensitizationUtil; 5 | 6 | import java.lang.reflect.Field; 7 | 8 | /** 9 | * @ClassName DesensitizationAppender 10 | * @Description 脱敏类 - 将日志进行脱敏 11 | * @Author 柳成荫 12 | * @Date 2021/1/9 13 | */ 14 | public class DesensitizationAppender { 15 | /** 16 | * LoggingEvent的属性 - message 17 | * 格式化前的日志信息,如log.info("your name : {}", "柳成荫") 18 | * message就是"your name : {}" 19 | */ 20 | private static final String MESSAGE = "message"; 21 | /** 22 | * LoggingEvent的属性 - formattedMessage 23 | * 格式化后的日志信息,如log.info("your name : {}", "柳成荫") 24 | * formattedMessage就是"your name : 柳成荫" 25 | */ 26 | private static final String FORMATTED_MESSAGE = "formattedMessage"; 27 | 28 | public void operation(LoggingEvent event) { 29 | // event.getArgumentArray() - 获取日志中的参数数组 30 | // 如:log.info("your name : {}, your id : {}", "柳成荫", 11) 31 | // event.getArgumentArray() => ["柳成荫",11] 32 | if (event.getArgumentArray() != null) { 33 | // 获取格式化后的Message 34 | String eventFormattedMessage = event.getFormattedMessage(); 35 | DesensitizationUtil util = new DesensitizationUtil(); 36 | // 获取替换后的日志信息 37 | String changeMessage = util.customChange(eventFormattedMessage); 38 | if (!(null == changeMessage || "".equals(changeMessage))) { 39 | try { 40 | // 利用反射的方式,将替换后的日志设置到原event对象中去 41 | Class eventClass = event.getClass(); 42 | // 保险起见,将message和formattedMessage都替换了 43 | Field message = eventClass.getDeclaredField(MESSAGE); 44 | message.setAccessible(true); 45 | message.set(event, changeMessage); 46 | Field formattedMessage = eventClass.getDeclaredField(FORMATTED_MESSAGE); 47 | formattedMessage.setAccessible(true); 48 | formattedMessage.set(event, changeMessage); 49 | } catch (IllegalAccessException | NoSuchFieldException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logback和slf4j的日志脱敏组件 2 | #### 前言 3 | 对于日志脱敏的方式有很多,常见的有①使用conversionRule标签,继承MessageConverter②书写一个脱敏工具类,在打印日志的时候对特定特字段进行脱敏返回。 4 | 两种方式各有优缺点:第一种方式需要修改代码,不符合开闭原则。第二种方式,需要在日志方法的参数进行脱敏,对原生日志有入侵行为。 5 | 6 | 7 | #### 本组件说明 8 | 本组件基于非入侵及脱敏字段扩展考虑,采用yml配置文件来扩展脱敏字段及不同的脱敏规则。其核心思想就是:读取配置文件里的脱敏字段和其脱敏规则。在日志替换的时候将字段进行脱敏。 9 | 本组件基于logback+slf4j,暂不支持log4j,如需解决log4j的日志,可以参照本组件的思想去完成。 10 | 本组件支持的脱敏数据类型:八大基本类型及其包装类型、String类型、Map、List、JSON字符串、项目中的POJO对象。 11 | 注:Map、JSON、List、POJO、List对象的脱敏处理需要配置其对应的字段名即可,无法处理List<八大类型+字符串>类型。 12 | 在使用本组件的时候,一定要注重日志打印规范。 13 | 14 | 具体使用方式见博客(重要!重要!重要!):https://blog.csdn.net/qq_40885085/article/details/113385261 15 | 16 | #### 匹配规则: 17 | key + 分割符 + value,如phone:{},即phone:13436781234。如email={},即email=123456789@qq.com 18 | 本组件默认只支持冒号和等号分割,如需其他方式可以修改正则匹配方式(本组件的正则匹配是匹配的key:value和key=value) 19 | 如: 20 | ```java 21 | log.info("your email:{}, your phone:{}", "123456789@qq.com","15310763497"); 22 | log.info("your email={}, your cellphone={}", "123456789@qq.com","15310763497"); 23 | ``` 24 | 25 | #### 打入本地仓库 26 | 我原本是想将Jar包发布在Github,使用Github作为Maven仓库。尝试过,但是没有成功(自己太菜了)。 27 | 因此,我只能将Jar包上传到Github,读者若需要可以直接下载Jar包打入本地仓库即可使用(也可以直接引入Jar包)。 28 | Jar包在/repo/pers/liuchengyin/logback-desensitization/1.0.0文件夹下,名为`logback-desensitization-1.0.0.jar` 29 | ##### 打入方法: 30 | 1、将jar包放入某个文件夹内,在这个文件夹内打开cmd(前提,Maven配置无误,可以使用mvn -v检查) 31 | 2、使用如下命令即可 32 | ```java 33 | mvn install:install-file -DgroupId=pers.liuchengyin -DartifactId=logback-desensitization -Dversion=1.0.0 -Dpackaging=jar -Dfile=logback-desensitization-1.0.0.jar 34 | ``` 35 | 3、命令说明 36 | ```java 37 | -DgroupId 38 | 表示jar对应的groupId 39 | pers.liuchengyin 40 | -DartifactId: 41 | 表示jar对应的artifactId 42 | logback-desensitization 43 | -Dversion 44 | 表示jar对应的 version 45 | 1.0.0 46 | ``` 47 | 48 | #### 使用方式 49 | 1、引入Jar包或其对应的pom依赖 50 | ```java 51 | 52 | pers.liuchengyin 53 | logback-desensitization 54 | 1.0.0 55 | 56 | ``` 57 | 2、配置logback-desensitize.yml 58 | 3、在logback.xml中引入对应的Appender,使用组件里的类代替原来的 59 | ###### ①ConsoleAppender - 控制台脱敏 60 | 原配置类: 61 | ```java 62 | ch.qos.logback.core.ConsoleAppender 63 | ``` 64 | 替换类: 65 | ```java 66 | pers.liuchengyin.logbackadvice.LcyConsoleAppender 67 | ``` 68 | ###### ②RollingFileAppender - 滚动文件 69 | 原配置类: 70 | ```java 71 | ch.qos.logback.core.rolling.RollingFileAppender 72 | ``` 73 | 替换类: 74 | ```java 75 | pers.liuchengyin.logbackadvice.LcyRollingFileAppender 76 | ``` 77 | ###### ③FileAppender - 文件 78 | 原配置类: 79 | ```java 80 | ch.qos.logback.core.FileAppender 81 | ``` 82 | 替换类: 83 | ```java 84 | pers.liuchengyin.logbackadvice.LcyFileAppender 85 | ``` 86 | -------------------------------------------------------------------------------- /repo/pers/liuchengyin/logback-desensitization/1.0.0/logback-desensitization-1.0.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.1 9 | 10 | 11 | pers.liuchengyin 12 | logback-desensitization 13 | 1.0.0 14 | LogbackDesensitization 15 | Logback Desensitization 16 | 17 | 18 | 1.8 19 | github 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter 26 | 27 | 28 | com.alibaba 29 | fastjson 30 | 1.2.8 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | maven-deploy-plugin 39 | 2.8.1 40 | 41 | internal.repo::default::file://${project.build.directory}/mvn-repo 42 | 43 | 44 | 45 | 46 | com.github.github 47 | site-maven-plugin 48 | 0.12 49 | 50 | Maven artifacts for ${project.version} 51 | true 52 | ${project.build.directory}/mvn-repo 53 | refs/heads/master 54 | true 55 | 56 | **/* 57 | 58 | LogbackDesensitization 59 | VaeIsMyIdol 60 | 61 | 62 | 63 | 64 | site 65 | 66 | deploy 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/java/pers/liuchengyin/utils/YmlUtils.java: -------------------------------------------------------------------------------- 1 | package pers.liuchengyin.utils; 2 | 3 | import java.io.InputStream; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Objects; 9 | import org.springframework.util.CollectionUtils; 10 | import org.yaml.snakeyaml.DumperOptions; 11 | import org.yaml.snakeyaml.Yaml; 12 | 13 | /** 14 | * @ClassName YmlUtils 15 | * @Description Yml配置文件操作相关 16 | * @Author 柳成荫 17 | * @Date 2021/1/9 18 | */ 19 | public class YmlUtils { 20 | /** 默认脱敏配置文件名 - 默认在resources目录下 */ 21 | public static String PROPERTY_NAME = "logback-desensitize.yml"; 22 | /** Key:pattern - 单规则 */ 23 | public static final String PATTERN = "pattern"; 24 | /** Key:patterns - 多规则 */ 25 | public static final String PATTERNS = "patterns"; 26 | /** Key:open - 是否开启脱敏 */ 27 | public static final String OPEN_FLAG = "open"; 28 | /** Key:ignore - 是否开启忽略大小写匹配 */ 29 | public static final String IGNORE = "ignore"; 30 | /** Key:脱敏配置文件头Key */ 31 | public static final String YML_HEAD_KEY = "log-desensitize"; 32 | /** key:patterns对应key下的规则Key */ 33 | public static final String CUSTOM = "custom"; 34 | /** Yml脱敏配置文件内容 - Map格式 */ 35 | public static Map patternMap; 36 | public static final DumperOptions OPTIONS = new DumperOptions(); 37 | 38 | 39 | static { 40 | OPTIONS.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); 41 | patternMap = getYmlByName(PROPERTY_NAME); 42 | } 43 | 44 | /** 45 | * 获取Yml配置文件的内容 - 以Map的格式 46 | * @param fileName Yml配置文件名 47 | * @return 配置信息(Map格式) 48 | */ 49 | private static Map getYmlByName(String fileName) { 50 | if (CollectionUtils.isEmpty(patternMap)) { 51 | Object fromYml = null; 52 | try { 53 | // 获取Yml配置文件的Map对象 54 | fromYml = getFromYml(fileName, YML_HEAD_KEY); 55 | // LinkedHashMap,如果不是Map类型(比如配置文件里只有log-desensitize=123456),直接返回patternMap本身 56 | if (fromYml instanceof Map) { 57 | return (Map)fromYml; 58 | } 59 | } catch (Exception e) { 60 | return null; 61 | } 62 | } 63 | return patternMap; 64 | } 65 | 66 | /** 67 | * 通过key获取value从yml配置文件 68 | * @param fileName Yml文件名 69 | * @param key key 70 | * @return value或者map本身 71 | */ 72 | public static Object getFromYml(String fileName, String key){ 73 | // 创建一个Yaml对象 74 | Yaml yaml = new Yaml(OPTIONS); 75 | // 获得流 76 | InputStream inputStream = YmlUtils.class.getClassLoader().getResourceAsStream(fileName); 77 | HashMap map = (HashMap)yaml.loadAs(inputStream, HashMap.class); 78 | // 如果map内有值,直接返回key对应的Value,否则返回map本身 79 | return Objects.nonNull(map) && map.size() > 0 ? map.get(key) : map; 80 | } 81 | 82 | /** 83 | * 获取key为pattern的值 84 | * @return pattern对应的map,或者null(如pattern=123这种情况) 85 | */ 86 | public static Map getPattern() { 87 | Object pattern = patternMap.get(PATTERN); 88 | if (pattern instanceof Map) { 89 | return (Map)pattern; 90 | } else { 91 | return null; 92 | } 93 | } 94 | 95 | /** 96 | * 获取所有pattern,含key为pattern,key为patterns 97 | * @return pattern 98 | */ 99 | public static Map getAllPattern() { 100 | Map allPattern = new HashMap(); 101 | Map pattern = getPattern(); 102 | Map patterns = getPatterns(); 103 | if (!CollectionUtils.isEmpty(patterns)) { 104 | allPattern.putAll(patterns); 105 | } 106 | // 注意:patterns中的key与pattern的key重复,patterns中的不生效(Map无重复Key) 107 | if (!CollectionUtils.isEmpty(pattern)) { 108 | allPattern.putAll(pattern); 109 | } 110 | return allPattern; 111 | } 112 | 113 | /** 114 | * 获取key为patterns的值 115 | * @return patterns对应的map,或者null(如patterns=123这种情况) 116 | */ 117 | public static Map getPatterns() { 118 | Map map = new HashMap(); 119 | Object patterns = patternMap.get(PATTERNS); 120 | // patterns下有多个key的时候(List) 121 | if (patterns instanceof List) { 122 | // 获取key为"patterns"的值(List>) 123 | List> list = (List>)patterns; 124 | if (!CollectionUtils.isEmpty(list)) { 125 | Iterator> iterator = list.iterator(); 126 | // 黄线强迫症,用for代替while 127 | for (;iterator.hasNext();){ 128 | Map maps = (Map)iterator.next(); 129 | assembleMap(map, maps); 130 | } 131 | return map; 132 | } 133 | } 134 | // patterns只有一个key的时候,且非List 135 | if (patterns instanceof Map) { 136 | assembleMap(map, (Map)patterns); 137 | return map; 138 | } else { 139 | return null; 140 | } 141 | } 142 | 143 | /** 144 | * 将patterns中每个key对应的规则按的方式放入map 145 | * @param map map 146 | * @param patterns patterns 147 | */ 148 | private static void assembleMap(Map map, Map patterns) { 149 | // 获取patterns里key值为"key"的值(脱敏关键字) 150 | Object key = patterns.get("key"); 151 | if (key instanceof String) { 152 | // 清除空格 153 | String keyWords = ((String) key).replace(" ", ""); 154 | // 以逗号分隔出一个key数组 155 | String[] keyArr = keyWords.split(","); 156 | for(String keyStr : keyArr){ 157 | map.put(keyStr, patterns.get(CUSTOM)); 158 | } 159 | } 160 | } 161 | 162 | /** 163 | * 是否开启脱敏,默认不开启 164 | * @return 是否开启脱敏 165 | */ 166 | public static Boolean getOpen() { 167 | Object flag = patternMap.get(OPEN_FLAG); 168 | return flag instanceof Boolean ? (Boolean)flag : false; 169 | } 170 | 171 | /** 172 | * 是否忽略大小写匹配,默认开启 173 | * @return 是否忽略大小写匹配 174 | */ 175 | public static Boolean getIgnore() { 176 | Object flag = patternMap.get(IGNORE); 177 | return flag instanceof Boolean ? (Boolean)flag : true; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/pers/liuchengyin/utils/DesensitizationUtil.java: -------------------------------------------------------------------------------- 1 | package pers.liuchengyin.utils; 2 | 3 | import org.springframework.util.CollectionUtils; 4 | import java.util.*; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * @ClassName DesensitizationUtil 10 | * @Description 脱敏工具类 11 | * @Author 柳成荫 12 | * @Date 2021/1/9 13 | */ 14 | public class DesensitizationUtil { 15 | /** 16 | * 正则匹配模式 - 该正则表达式第三个()可能无法匹配以某些特殊符号开头和结尾的(如果像密码这种字段,前后如果有很多特殊字段,则无法匹配,建议密码直接加密,无需脱敏) 17 | */ 18 | public static final Pattern REGEX_PATTERN = Pattern.compile("\\s*([\"]?[\\w]+[\"]?)(\\s*[:=]+[^\\u4e00-\\u9fa5@,.*{\\[\\w]*\\s*)([\\u4e00-\\u9fa5_\\-@.\\w]+)[\\W&&[^\\-@.]]?\\s*"); 19 | // 该正则表达式第三个()可以匹配以某些特殊字符开头和结尾的,但是对于日志来说,处理也很麻烦 20 | // public static final Pattern REGEX_PATTERN = Pattern.compile("\\s*([\"]?[\\w]+[\"]?)(\\s*[::=><]+\\s*)([\\S]+[\\u4e00-\\u9fa5\\w]+[\\S]+)[\\W&&[^\\-@.]]?\\s*"); 21 | 22 | /** 23 | * 匹配非数字 24 | */ 25 | public static final Pattern REGEX_NUM = Pattern.compile("[^0-9]"); 26 | 27 | /** 28 | * 是否开启脱敏 29 | */ 30 | public static Boolean openFlag = false; 31 | /** 32 | * 是否忽略key的大小写 33 | */ 34 | public static Boolean ignoreFlag = true; 35 | /** 36 | * 作为ignoreFlag初始的标记 37 | */ 38 | private static Boolean initIgnoreFlag = false; 39 | /** 40 | * 作为openFlag初始化的标记 41 | */ 42 | private static Boolean initOpenFlag = false; 43 | /** 44 | * 所有key:value配置匹配对 45 | */ 46 | public static Map allPattern; 47 | /** 48 | * key为全小写的allPattern - pattern和patterns 49 | */ 50 | public static Map lowerCaseAllPattern; 51 | /** 52 | * 手机 53 | */ 54 | public static final String PHONE = "phone"; 55 | /** 56 | * 邮箱 57 | */ 58 | public static final String EMAIL = "email"; 59 | /** 60 | * 身份证 61 | */ 62 | public static final String IDENTITY = "identity"; 63 | /** 64 | * 自定义 65 | */ 66 | public static final String OTHER = "other"; 67 | /** 68 | * 密码 69 | */ 70 | public static final String PASSWORD = "password"; 71 | 72 | /** 73 | * 将event对象的formattedMessage脱敏 74 | * 75 | * @param eventFormattedMessage LoggingEvent的formattedMessage属性 76 | * @return 脱敏后的日志信息 77 | */ 78 | public String customChange(String eventFormattedMessage) { 79 | try { 80 | // 原始信息 - 格式化后的 81 | String originalMessage = eventFormattedMessage; 82 | boolean flag = false; 83 | // 获取Yml配置文件内容 - Map格式 84 | Map patternMap = YmlUtils.patternMap; 85 | if (!CollectionUtils.isEmpty(patternMap)) { 86 | // 如果没有开启脱敏,返回"",则不会做脱敏操作 87 | if (!this.checkOpen(patternMap)) { 88 | return ""; 89 | } 90 | // 获取一个原始Message的正则匹配器 91 | Matcher regexMatcher = REGEX_PATTERN.matcher(eventFormattedMessage); 92 | // 如果部分匹配(一个对象/JSON字符串/Map/List<对象/Map>等会有多个匹配),就根据分组来获取key和value 93 | while (regexMatcher.find()) { 94 | // group(1)就是key,group(2)就是分隔符(如:和=),group(3)就是value 95 | try { 96 | // 获取key - 将引号替换掉,去掉两边空格(JSON字符串去引号) 97 | String key = regexMatcher.group(1).replaceAll("\"", "").trim(); 98 | // 获取原始Value 99 | String originalValue = regexMatcher.group(3); 100 | // 获取Key对应规则 101 | Object keyPatternValue = this.getKeyIgnoreCase(key); 102 | if (null != keyPatternValue && null != originalValue && !"null".equals(originalValue)) { 103 | // 将原始Value - 引号替换掉,去掉两边空格(JSON字符串去引号) 104 | String value = originalValue.replaceAll("\"", "").trim(); 105 | if (!"null".equals(value) || value.equalsIgnoreCase(key)) { 106 | String patternVales = getMultiplePattern(keyPatternValue, value); 107 | if ("".equals(patternVales)) { 108 | // 不符规则/没有规则的不能影响其他符合规则的 109 | continue; 110 | } 111 | patternVales = patternVales.replaceAll(" ", ""); 112 | if(PASSWORD.equalsIgnoreCase(patternVales)){ 113 | String origin = regexMatcher.group(1) + regexMatcher.group(2) + regexMatcher.group(3); 114 | originalMessage = originalMessage.replace(origin, regexMatcher.group(1) + regexMatcher.group(2) + "******"); 115 | flag=true; 116 | // 密码级别的,直接替换为全*,继续下一轮匹配 117 | continue; 118 | } 119 | // 原始的规则(完整) 120 | String originalPatternValues = patternVales; 121 | // 判断这个规则是否带括号,带括号的需要把括号拿出来 - 核心规则 122 | String filterData = this.getBracketPattern(patternVales); 123 | if (!"".equals(filterData)) { 124 | patternVales = filterData; 125 | } 126 | // 以逗号分割 127 | String[] split = patternVales.split(","); 128 | value = getReplaceValue(value, patternVales, split, originalPatternValues); 129 | if (value != null && !"".equals(value)) { 130 | flag = true; 131 | String origin = regexMatcher.group(1) + regexMatcher.group(2) + regexMatcher.group(3); 132 | originalMessage = originalMessage.replace(origin, regexMatcher.group(1) + regexMatcher.group(2) + value); 133 | } 134 | } 135 | } 136 | } catch (Exception e) { 137 | // 捕获到异常,直接返回结果(空字符串) - 这个异常可能发生的场景:同时开启控制台和输出文件的时候 138 | // 当控制台进行一次脱敏之后,文件的再去脱敏,是对脱敏后的message脱敏,则正则匹配会出现错误 139 | // 比如123456789@.com 脱敏后:123***456789@qq.com,正则匹配到123,这个123去substring的时候会出错 140 | return ""; 141 | } 142 | } 143 | } 144 | return flag ? originalMessage : ""; 145 | } catch (Exception e) { 146 | return ""; 147 | } 148 | } 149 | 150 | 151 | /** 152 | * 获取替换后的value 153 | * @param value value 154 | * @param patternVales 核心规则 155 | * @param split 分割 156 | * @param originalPatternValues 原始规则 157 | * @return 158 | */ 159 | private String getReplaceValue(String value, String patternVales, String[] split, String originalPatternValues) { 160 | if (split.length >= 2 && !"".equals(patternVales)) { 161 | String append = ""; 162 | String start = REGEX_NUM.matcher(split[0]).replaceAll(""); 163 | String end = REGEX_NUM.matcher(split[1]).replaceAll(""); 164 | int startSub = Integer.parseInt(start) - 1; 165 | int endSub = Integer.parseInt(end) - 1; 166 | // 脱敏起点/结尾符下标 167 | int index; 168 | String flagSub; 169 | int indexOf; 170 | int newValueL; 171 | String newValue; 172 | // 脱敏结尾 173 | if (originalPatternValues.contains(">")) { 174 | // 获取>的下标 175 | index = originalPatternValues.indexOf(">"); 176 | // 获取标志符号 177 | flagSub = originalPatternValues.substring(0, index); 178 | // 获取标志符号的下标 179 | indexOf = value.indexOf(flagSub); 180 | // 获取标志符号前面数据 181 | newValue = value.substring(0, indexOf); 182 | // 获取数据的长度 183 | newValueL = newValue.length(); 184 | // 获取标识符及后面的数据 185 | append = value.substring(indexOf); 186 | value = this.dataDesensitization(Math.max(startSub, 0), endSub >= 0 ? (endSub <= newValueL ? endSub : newValueL - 1) : 0, newValue) + append; 187 | } else if (originalPatternValues.contains("<")) { 188 | // 脱敏起点 189 | index = originalPatternValues.indexOf("<"); 190 | flagSub = originalPatternValues.substring(0, index); 191 | indexOf = value.indexOf(flagSub); 192 | newValue = value.substring(indexOf + 1); 193 | newValueL = newValue.length(); 194 | append = value.substring(0, indexOf + 1); 195 | value = append + this.dataDesensitization(Math.max(startSub, 0), endSub >= 0 ? (endSub <= newValueL ? endSub : newValueL - 1) : 0, newValue); 196 | } else if (originalPatternValues.contains(",")) { 197 | newValueL = value.length(); 198 | value = this.dataDesensitization(Math.max(startSub, 0), endSub >= 0 ? (endSub <= newValueL ? endSub : newValueL - 1) : 0, value); 199 | } 200 | } else if (!"".equals(patternVales)) { 201 | int beforeIndexOf = patternVales.indexOf("*"); 202 | int last = patternVales.length() - patternVales.lastIndexOf("*"); 203 | int lastIndexOf = value.length() - last; 204 | value = this.dataDesensitization(beforeIndexOf, lastIndexOf, value); 205 | } 206 | return value; 207 | } 208 | 209 | /** 210 | * 根据key获取对应的规则(也许是Map,也许是String) 211 | * 212 | * @param key key 213 | * @return key对应的规则(也许是Map , 也许是String) 214 | */ 215 | private Object getKeyIgnoreCase(String key) { 216 | // 获取所有pattern 217 | if (CollectionUtils.isEmpty(allPattern)) { 218 | allPattern = YmlUtils.getAllPattern(); 219 | } 220 | // 作为ignoreFlag初始化的标记,第一次ignoreFlag需要从Yml中获取是否开启 221 | // 后面就不用去Yml里获取了 222 | if (!initIgnoreFlag) { 223 | initIgnoreFlag = true; 224 | // 仅在第一次会去获取,无论true还是false(默认是开启忽略大小写) 225 | ignoreFlag = YmlUtils.getIgnore(); 226 | if (ignoreFlag) { 227 | // 如果忽略大小写,就去获取一份key小写化的allPattern 228 | lowerCaseAllPattern = this.transformUpperCase(allPattern); 229 | } 230 | } 231 | // 只有忽略大小写的时候,才去从lowerCaseAllPattern里获取 232 | if (ignoreFlag) { 233 | return lowerCaseAllPattern.get(key.toLowerCase()); 234 | } else { 235 | // 否则从原始的pattern中取 236 | return allPattern.get(key); 237 | } 238 | } 239 | 240 | 241 | 242 | /** 243 | * 将pattern的key值全部转换为小写 244 | * 245 | * @param pattern pattern 246 | * @return 转换后的pattern 247 | */ 248 | public Map transformUpperCase(Map pattern) { 249 | Map resultMap = new HashMap(); 250 | if (pattern != null && !pattern.isEmpty()) { 251 | // 获取Key的Set集合 252 | Set keySet = pattern.keySet(); 253 | Iterator iterator = keySet.iterator(); 254 | // 黄线强迫症,用for代替while 255 | for (; iterator.hasNext(); ) { 256 | String key = iterator.next(); 257 | // 把key转换为小写字符串 258 | String newKey = key.toLowerCase(); 259 | // 重新放入 260 | resultMap.put(newKey, pattern.get(key)); 261 | } 262 | } 263 | return resultMap; 264 | } 265 | 266 | 267 | /** 268 | * 获取规则字符串 269 | * 270 | * @param patternVale 规则 271 | * @param newValue key对应的值 - 如 name:liuchengyin 这个参数就是liuchengyn 272 | * @return 规则的字符串 273 | */ 274 | private String getMultiplePattern(Object patternVale, String newValue) { 275 | if (patternVale instanceof String) { 276 | // 如果规则是String类型,直接转换为String类型返回 277 | return (String) patternVale; 278 | } else if (patternVale instanceof Map) { 279 | // 获取规则 - Map类型(不推荐,有风险) 280 | return this.getPatternByMap((Map) patternVale, newValue); 281 | } else { // 获取规则 - List类型,一个Key可能有多种匹配规则 282 | if (patternVale instanceof List) { 283 | List> list = (List>) patternVale; 284 | if (!CollectionUtils.isEmpty(list)) { 285 | Iterator> iterator = list.iterator(); 286 | // 遍历每一种规则 287 | for (; iterator.hasNext(); ) { 288 | Map map = iterator.next(); 289 | String patternValue = this.getPatternByMap(map, newValue); 290 | // 如果是空的,表示没匹配上该规则,去匹配下一个规则 291 | if (!"".equals(patternValue)) { 292 | return patternValue; 293 | } 294 | } 295 | } 296 | } 297 | return ""; 298 | } 299 | } 300 | 301 | 302 | /** 303 | * 获取规则 304 | * 305 | * @param map 规则 306 | * @param value key对应的值 - 如 name:liuchengyin 这个参数就是liuchengyn 307 | * @return 308 | */ 309 | private String getPatternByMap(Map map, String value) { 310 | if (CollectionUtils.isEmpty(map)) { 311 | // 为空就是无规则 312 | return ""; 313 | } else { 314 | // 获取匹配规则 - 自定义规则(正则) 315 | Object customRegexObj = map.get("customRegex"); 316 | // 获取脱敏方式 317 | Object positionObj = map.get("position"); 318 | // 获取匹配规则 - 自定义规则(正则) 319 | String customRegex = ""; 320 | // position必须有 321 | String position = ""; 322 | if (customRegexObj instanceof String) { 323 | customRegex = (String) customRegexObj; 324 | } 325 | if (positionObj instanceof String) { 326 | position = (String) positionObj; 327 | } 328 | // 如果日志中的值能够匹配,直接返回其对应的规则 329 | if (!"".equals(customRegex) && value.matches(customRegex)) { 330 | return position; 331 | } else { 332 | // 如果不能匹配到正则,就看他是不是内置规则 333 | Object defaultRegexObj = map.get("defaultRegex"); 334 | String defaultRegex = ""; 335 | if (defaultRegexObj instanceof String) { 336 | defaultRegex = (String) defaultRegexObj; 337 | } 338 | // 这段代码写的多多少少感觉有点问题,可以写在一个if里,但是阿里检测代码的工具会警告 339 | if (!"".equals(defaultRegex)) { 340 | if(IDENTITY.equals(defaultRegex) && isIdentity(value)){ 341 | return position; 342 | }else if(EMAIL.equals(defaultRegex) && isEmail(value)){ 343 | return position; 344 | }else if(PHONE.equals(defaultRegex) && isMobile(value)){ 345 | return position; 346 | }else if(OTHER.equals(defaultRegex)){ 347 | return position; 348 | } 349 | } 350 | return ""; 351 | } 352 | } 353 | } 354 | 355 | 356 | /** 357 | * 获取规则 - 判断是否带括号,带括号则返回括号内数据 358 | * @param patternVales 规则 359 | * @return 规则 360 | */ 361 | private String getBracketPattern(String patternVales) { 362 | // 是否存在括号 363 | if (patternVales.contains("(")) { 364 | int startCons = patternVales.indexOf("("); 365 | int endCons = patternVales.indexOf(")"); 366 | patternVales = patternVales.substring(startCons + 1, endCons); 367 | return patternVales; 368 | } else { 369 | return ""; 370 | } 371 | } 372 | 373 | public static boolean isEmail(String str) { 374 | return str.matches("^[\\w-]+@[\\w-]+(\\.[\\w-]+)+$"); 375 | } 376 | 377 | public static boolean isIdentity(String str) { 378 | return str.matches("(^\\d{18}$)|(^\\d{15}$)"); 379 | } 380 | 381 | public static boolean isMobile(String str) { 382 | return str.matches("^1[0-9]{10}$"); 383 | } 384 | 385 | /** 386 | * 检查是否开启脱敏 387 | * 388 | * @param pattern Yml配置文件内容 - Map格式 389 | * @return 是否开启脱敏 390 | */ 391 | private Boolean checkOpen(Map pattern) { 392 | // 作为openFlag初始化的标记,第一次openFlag需要从Yml中获取是否开启 393 | // 后面就不用去Yml里获取了 394 | if (!initOpenFlag) { 395 | initOpenFlag = true; 396 | // 仅在第一次会去获取 397 | openFlag = YmlUtils.getOpen(); 398 | } 399 | // 第二次以后openFlag已经有值,无论true还是false(默认是未开启) 400 | return openFlag; 401 | } 402 | 403 | 404 | /** 405 | * 脱敏处理 406 | * @param start 脱敏开始下标 407 | * @param end 脱敏结束下标 408 | * @param value value 409 | * @return 410 | */ 411 | public String dataDesensitization(int start, int end, String value) { 412 | char[] chars; 413 | int i; 414 | // 正常情况 - end在数组长度内 415 | if (start >= 0 && end + 1 <= value.length()) { 416 | chars = value.toCharArray(); 417 | // 脱敏替换 418 | for (i = start; i < chars.length && i < end + 1; ++i) { 419 | chars[i] = '*'; 420 | } 421 | return new String(chars); 422 | } else if (start >= 0 && end >= value.length()) { 423 | // 非正常情况 - end在数组长度外 424 | chars = value.toCharArray(); 425 | for (i = start; i < chars.length; ++i) { 426 | chars[i] = '*'; 427 | } 428 | return new String(chars); 429 | } else { 430 | // 不符要求,不脱敏 431 | return value; 432 | } 433 | } 434 | } --------------------------------------------------------------------------------