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;
}
}
logback 日志脱敏处理的更多相关文章
- java 日志脱敏框架 sensitive,优雅的打印脱敏日志
问题 为了保证用户的信息安全,敏感信息需要脱敏. 项目开发过程中,每次处理敏感信息的日志问题感觉很麻烦,大部分都是用工具类单独处理,不利于以后统一管理,很不优雅. 于是,就写了一个基于 java 注解 ...
- Logback日志系统配置攻略
logback是log4j作者推出的新日志系统,原生支持slf4j通用日志api,允许平滑切换日志系统,并且对简化应用部署中日志处理的工作做了有益的封装. 官方地址为:http://logback.q ...
- lombok+slf4j+logback SLF4J和Logback日志框架详解
maven 包依赖 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lomb ...
- mybatis用logback日志不显示sql的解决办法
mybatis用logback日志不显示sql的解决方法 1.mybatis-config.xml的设定 关于logimpl的设定值还不支持logback,如果用SLF4J是不好用的. 这是官方文档的 ...
- log4j 日志脱敏处理 + java properties文件加载
Java 加载Properties 配置文件: ResourceBundle bundle = ResourceBundle.getBundle("log4j_filter"); ...
- Logback日志配置的简单使用
Logback介绍 Logback是由log4j创始人设计的又一个开源日志组件.logback当前分成三个模块:logback-core,logback- classic和logback-access ...
- 在SpringBoot中添加Logback日志处理
前言 SpringBoot项目中在官方文档中说明,默认已经依赖了一些日志框架.而其中推荐使用的就是Logback,所以这一次我将在我的模版中加入Logback日志的配置,说明一下,SpringBoot ...
- 剑指架构师系列-spring boot的logback日志记录
Spring Boot集成了Logback日志系统. Logback的核心对象主要有3个:Logger.Appender.Layout 1.Logback Logger:日志的记录器 主要用于存放日志 ...
- Logback日志基础配置以及自定义配置
Logback日志基础配置 logback日志配置有很多介绍,但是有几个非常基础的,容易忽略的.下面是最简单的一个配置,注意加粗的描述 <?xml version="1.0" ...
- shell实战之日志脱敏
本次实战目标为日志脱敏,将日志目录内的所有文件进行处理,凡是涉及到卡号和密码的信息,一律以“*”号替代,要替代的内容都从对应的标签内获取,本脚本执行目录 drwxr-xr-x 5 root root ...
随机推荐
- kvm 透传显卡至win10虚拟机
环境 已安装nvidia 显卡 驱动 操作系统:CentOS Linux release 7.9.2009 (Core) 内核版本:Linux 5.4.135-1.el7.elrepo.x86_64 ...
- vmware网络连接
vmware提供桥接模式网络连接.网络地址转换 (NAT).仅主机模式网络连接和自定义网络连接选项,用于为虚拟机配置虚拟网络连接.在安装 vmware 时,已在主机系统中安装用于所有网络连接配置的软件 ...
- System.IO.FileNotFoundException: Could not load file or assembly 'System.IO.Compression.FileSystem系统找不到指定的文件
错误:System.IO.FileNotFoundException: Could not load file or assembly 'System.IO.Compression.FileSyste ...
- GitLab能通过ssh克隆无法通过http克隆,也无法进行流水线,提示port 80: Connection refused
问题记录:VM-Ubuntu20.04刚开始时使用NAT模式连接,后来改成桥接模式,改完之后原来使用docker启动的gitlab服务无法克隆,一直提示:fatal...没有远程库什么的,没解决后来删 ...
- 【Frida】启动手机上的Frida插件
运行以下命令可以保证Frida一直在手机上运行,不关机,Frida就不关 adb shell su -c "./data/local/frida-server-15.2.2-android- ...
- 初识swoole
环境: 腾讯云服务器 centos7 在安装完swoole服务之后 使用 php -m 查看是否有该组件 确认存在后 在根目录下 创建一个文件夹 当做专门测试swoole使用 如 8 在该文件夹下 ...
- 关于easyocr、paddleocr、cnocr之比较
关于easyocr.paddleocr.cnocr之比较 EasyOCR 是一个使用 Java 语言实现的 OCR 识别引擎(基于Tesseract).借助几个简单的API,即能使用Java语言完成图 ...
- Linux的常用命令符标注
1.who命令--显示目前登录系统的用户信息. 语法:who(选项)(参数)参数:查询的文件 常用选项:-h:显示各栏位的标题信息列. -w:显示用户的信息状态栏. -q:显示登陆入系统的账号名称和总 ...
- k8s如何配置secret保存harbor仓库账号密码、pod中怎么使用harbor仓库镜像
转载: https://blog.csdn.net/MssGuo/article/details/127312239
- (原创)odoo15(master)下,列表导出权限控制
列表导出增加一个内置用户组"base.group_allow_export"以增强权限控制.