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 ...
随机推荐
- Too many requests in 1 hour. Try again later.的解决办法
原因 你的梯子用的人太多了,openAI本身就有问答频率限制. 解决办法 换个相对独立,没那么多人用的梯子,找个"安静点"的地区.
- 在IIS上同站点部署多个程序操作步骤
1.打开IIS管理器: 2.右击选中"网站",选择"添加网站":输入网站名称.路径.IP地址等信息: 3.确定后并访问网站: 部署后可能会因为框架不支持导致报错 ...
- VSCODE 界面设置
如上图所示: 用插件background-cover ,再设置下图片路径即可,程序员专属的开发DIY界面随手可得 当然添加账号来同步,以后设置一次即可随时同步
- Linux,Lnmp配置Index of /索引页
其实很简单就是修改conf配置文件 进入域名对应的vhost修改conf文件: 第一种方法 cd /usr/local/nginx/vhost/ 在清楚当前域名所对用的conf文件可以试用 ll 列出 ...
- win10 系统修复IE11方法
我也是手贱卸载了IE11,启用或关闭Windows功能里也没有Internet Explorer 11,今天意外发现了解决办法. 设置--应用--应用和功能--管理可选功能--添加功能--Intern ...
- django源码剖析(steup、runserver、生命周期)
工作上会经常用到不熟悉的第三方模块,大多数时候会选择看文档.百度谷歌.看源码等形式去把它用起来.几年工作经验下来源码看的不少了,但当面试被问到django的生命周期时,只能浅谈根据wsgi协议会走ap ...
- flask-基础篇03 请求钩子与上下文
一.异常处理 1.HTTP 异常主动抛出 ①abort 方法: 抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到页面未找到异常来终止请求,你可以调用 abo ...
- js获取各种高度的方法
js获取各种高度的方法 源文章:https://www.cnblogs.com/MrzhangRecord/p/9185868.html 目录 js获取各种高度的方法 1.获取元素的高度 模板:htm ...
- 7.29-bug计算器
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8 ...
- SpringMVC的学习day01
什么是SpringMVC 基于狂神说SpringMVC课程的学习 前面已经学习了spring.javaSE.javaweb.mybatis等知识,今天开始学习springMVC,是ssm框架的最后一个 ...