Appearance
十八、配置文件、接口、日志数据脱敏
核心隐私数据无论对于企业还是用户来说尤其重要,因此要想办法杜绝各种隐私数据的泄漏。
1、配置文件脱敏
经常会遇到这样一种情况:项目的配置文件中总有一些敏感信息,比如数据源的url、用户名、密码....这些信息一旦被暴露那么整个数据库都将会被泄漏,那么如何将这些配置隐藏呢?
以前都是手动将加密之后的配置写入到配置文件中,提取的时候再手动解密,当然这是一种思路,也能解决问题,但是每次都要手动加密、解密不觉得麻烦吗?
今天介绍一种方案,让你在无感知的情况下实现配置文件的加密、解密。利用一款开源插件:jasypt-spring-boot
。项目地址如下:
url
https://github.com/ulisesbocchio/jasypt-spring-boot
使用方法很简单,整合Spring Boot 只需要添加一个starter
。
xml
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
配置秘钥
在配置文件中添加一个加密的秘钥(任意),如下:
yaml
jasypt:
encryptor:
password: dGNtLW1hbmFnZS1zeXN0ZW
当然将秘钥直接放在配置文件中也是不安全的,我们可以在项目启动的时候配置秘钥,命令如下:
sh
java -jar xxx.jar -Djasypt.encryptor.password=dGNtLW1hbmFnZS1zeXN0ZW
生成加密后的数据
这一步骤是将配置明文进行加密,代码如下:
java
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringbootJasyptApplicationTests {
/**
* 注入加密方法
*/
@Autowired
private StringEncryptor encryptor;
/**
* 手动生成密文,此处演示了url,user,password
*/
@Test
public void encrypt() {
String url = encryptor.encrypt("jdbc\\:mysql\\://127.0.0.1\\:3306/test?useUnicode\\=true&characterEncoding\\=UTF-8&zeroDateTimeBehavior\\=convertToNull&useSSL\\=false&allowMultiQueries\\=true&serverTimezone=Asia/Shanghai");
String name = encryptor.encrypt("root");
String password = encryptor.encrypt("mac_root");
System.out.println("database url: " + url);
System.out.println("database name: " + name);
System.out.println("database password: " + password);
Assert.assertTrue(url.length() > 0);
Assert.assertTrue(name.length() > 0);
Assert.assertTrue(password.length() > 0);
}
}
也可以这样
java
public static void main(String[] args) {
BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
//加密所需的密钥
textEncryptor.setPassword("dGNtLW1hbmFnZS1zeXN0ZW0");
//要加密的数据(数据库的用户名或密码)
String username = textEncryptor.encrypt("root");
String password = textEncryptor.encrypt("mac_root");
String url = textEncryptor.encrypt("jdbc:mysql://10.18.104.78:4000/testdb?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true");
System.out.println("username:"+username);
System.out.println("password:"+password);
System.out.println("url:"+url);
}
将加密后的密文写入配置
jasypt
默认使用ENC()
包裹,此时的数据源配置如下:
yaml
spring:
datasource:
# 数据源基本配置
username: ENC(ZB2JtabneY/cuH560064Ow==)
password: ENC(1GRqh9U2S63VDkTUdwA5BQkVrYlAdVES)
driver-class-name: com.mysql.jdbc.Driver
url: ENC(d7hUejIq1LhYzbt86uXOw5ZMR9Ko6MUnXxxhFQV2XpSFLPQOSRsu2RAQcdTHagkQnE581NbBL4vZwFl89hn/I8EIiQjISvqceuRnFZ9B7XgJ8OAMqJndWwn+/F3rzF1kmT6OxzeQkd2M+oGjeEeK8EnrE/3TOG5zEvUTuMNc7I9zyWL1v27LtCdRCw0LYwRU2OsfNyem0IonDo/ihXawJcVpRG1RMz0xaCYkE7gVpWN0s8VW9diCxg==)
type: com.alibaba.druid.pool.DruidDataSource
上述配置是使用默认的prefix=ENC(
、suffix=)
,当然我们可以根据自己的要求更改,只需要在配置文件中更改即可,如下:
yaml
jasypt:
encryptor:
## 指定前缀、后缀
property:
prefix: 'PASS('
suffix: ')'
那么此时的配置就必须使用PASS()
包裹才会被解密,如下:
yaml
spring:
datasource:
# 数据源基本配置
username: PASS(ZB2JtabneY/cuH560064Ow==)
password: PASS(1GRqh9U2S63VDkTUdwA5BQkVrYlAdVES)
driver-class-name: com.mysql.jdbc.Driver
url: PASS(d7hUejIq1LhYzbt86uXOw5ZMR9Ko6MUnXxxhFQV2XpSFLPQOSRsu2RAQcdTHagkQnE581NbBL4vZwFl89hn/I8EIiQjISvqceuRnFZ9B7XgJ8OAMqJndWwn+/F3rzF1kmT6OxzeQkd2M+oGjeEeK8EnrE/3TOG5zEvUTuMNc7I9zyWL1v27LtCdRCw0LYwRU2OsfNyem0IonDo/ihXawJcVpRG1RMz0xaCYkE7gVpWN0s8VW9diCxg==)
type: com.alibaba.druid.pool.DruidDataSource
2、接口返回数据脱敏一
1、自定义一个Jackson注解
需要自定义一个脱敏注解,一旦有属性被标注,则进行对应得脱敏,如下:
java
package com.xx.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.xx.enums.PrivacyTypeEnum;
import com.xx.serializer.PrivacySerializer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:45
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
// 该注解使用序列化的方式
@JsonSerialize(using = PrivacySerializer.class)
public @interface PrivacyEncrypt {
/**
* 脱敏数据类型(没给默认值,所以使用时必须指定type)
*/
PrivacyTypeEnum type();
/**
* 前置不需要打码的长度
*/
int prefixNoMaskLen() default 1;
/**
* 后置不需要打码的长度
*/
int suffixNoMaskLen() default 1;
/**
* 用什么打码
*/
String symbol() default "*";
}
2、定制脱敏策略
java
package com.xx.enums;
import lombok.Getter;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:46
*/
@Getter
public enum PrivacyTypeEnum {
/** 自定义(此项需设置脱敏的范围)*/
CUSTOMER,
/** 姓名 */
NAME,
/** 身份证号 */
ID_CARD,
/** 手机号 */
PHONE,
/** 邮箱 */
EMAIL,
}
3、定制JSON序列化实现
下面将是重要实现,对标注注解@PrivacyEncrypt
的字段进行脱敏,实现如下:
java
package com.xx.serializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.xx.annotation.PrivacyEncrypt;
import com.xx.enums.PrivacyTypeEnum;
import com.xx.utils.PrivacyUtil;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.util.Objects;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:45
*/
@NoArgsConstructor
@AllArgsConstructor
public class PrivacySerializer extends JsonSerializer<String> implements ContextualSerializer {
/**
* 脱敏类型
*/
private PrivacyTypeEnum privacyTypeEnum;
/**
* 前几位不脱敏
*/
private Integer prefixNoMaskLen;
/**
* 最后几位不脱敏
*/
private Integer suffixNoMaskLen;
/**
* 用什么打码
*/
private String symbol;
@Override
public void serialize(String origin, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (StringUtils.isNotBlank(origin) && null != privacyTypeEnum) {
switch (privacyTypeEnum) {
case CUSTOMER:
jsonGenerator.writeString(PrivacyUtil.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, symbol));
break;
case NAME:
jsonGenerator.writeString(PrivacyUtil.hideChineseName(origin));
break;
case ID_CARD:
jsonGenerator.writeString(PrivacyUtil.hideIDCard(origin));
break;
case PHONE:
jsonGenerator.writeString(PrivacyUtil.hidePhone(origin));
break;
case EMAIL:
jsonGenerator.writeString(PrivacyUtil.hideEmail(origin));
break;
default:
throw new IllegalArgumentException("unknown privacy type enum " + privacyTypeEnum);
}
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
PrivacyEncrypt privacyEncrypt = beanProperty.getAnnotation(PrivacyEncrypt.class);
if (privacyEncrypt == null) {
privacyEncrypt = beanProperty.getContextAnnotation(PrivacyEncrypt.class);
}
if (privacyEncrypt != null) {
return new PrivacySerializer(privacyEncrypt.type(), privacyEncrypt.prefixNoMaskLen(),
privacyEncrypt.suffixNoMaskLen(), privacyEncrypt.symbol());
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
}
4、定义Person类,对其数据脱敏
使用注解@Sensitive
注解进行数据脱敏,代码如下:
java
package com.xx.model;
import com.xx.annotation.PrivacyEncrypt;
import com.xx.annotation.Sensitive;
import com.xx.enums.PrivacyTypeEnum;
import com.xx.enums.SensitiveStrategy;
import lombok.Data;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:36
*/
@Data
public class Person {
/**
* 真实姓名
*/
private String realName;
/**
* 地址
*/
private String address;
/**
* 电话号码
*/
@PrivacyEncrypt(type = PrivacyTypeEnum.PHONE)
private String phoneNumber;
/**
* 身份证号码
*/
@PrivacyEncrypt(type = PrivacyTypeEnum.ID_CARD)
private String idCard;
}
5、模拟接口测试
java
@RestController
public class TestController {
@GetMapping("/test")
public Person test() {
Person user = new Person();
user.setRealName("小薛博客");
user.setPhoneNumber("17683456578");
user.setAddress("广东省深圳市南山区....");
user.setIdCard("4333333333334334333");
return user;
}
}
json
{
"realName": "小薛博客",
"address": "广东省深圳市南山区....",
"phoneNumber": "176****6578",
"idCard": "4333****34333"
}
3、接口返回数据脱敏二
1、自定义一个Jackson注解
需要自定义一个脱敏注解,一旦有属性被标注,则进行对应得脱敏,如下:
java
package com.xx.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.xx.enums.SensitiveStrategy;
import com.xx.serializer.SensitiveJsonSerializer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:34
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive {
//脱敏策略
SensitiveStrategy strategy();
}
2、定制脱敏策略
针对项目需求,定制不同字段的脱敏规则,比如手机号中间几位用*
替代,如下:
java
package com.xx.enums;
import java.util.function.Function;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:35
*/
public enum SensitiveStrategy {
/**
* 用户名
*/
USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
/**
* 身份证
*/
ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
/**
* 手机号
*/
PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
/**
* 地址
*/
ADDRESS(s -> s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****"));
private final Function<String, String> desensitizer;
SensitiveStrategy(Function<String, String> desensitizer) {
this.desensitizer = desensitizer;
}
public Function<String, String> desensitizer() {
return desensitizer;
}
}
3、定制JSON序列化实现
下面将是重要实现,对标注注解@Sensitive
的字段进行脱敏,实现如下:
java
package com.xx.serializer;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.xx.annotation.Sensitive;
import com.xx.enums.SensitiveStrategy;
import java.io.IOException;
import java.util.Objects;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:35
*/
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveStrategy strategy;
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(strategy.desensitizer().apply(value));
}
/**
* 获取属性上的注解属性
*/
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation)&&Objects.equals(String.class, property.getType().getRawClass())) {
this.strategy = annotation.strategy();
return this;
}
return prov.findValueSerializer(property.getType(), property);
}
}
4、定义Person类,对其数据脱敏
使用注解@Sensitive
注解进行数据脱敏,代码如下:
java
package com.xx.model;
import com.xx.annotation.Sensitive;
import com.xx.enums.SensitiveStrategy;
import lombok.Data;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:36
*/
@Data
public class Person {
/**
* 真实姓名
*/
@Sensitive(strategy = SensitiveStrategy.USERNAME)
private String realName;
/**
* 地址
*/
@Sensitive(strategy = SensitiveStrategy.ADDRESS)
private String address;
/**
* 电话号码
*/
@Sensitive(strategy = SensitiveStrategy.PHONE)
private String phoneNumber;
/**
* 身份证号码
*/
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
private String idCard;
}
5、模拟接口测试
java
@RestController
public class TestController {
@GetMapping("/test")
public Person test() {
Person user = new Person();
user.setRealName("小薛博客");
user.setPhoneNumber("17683456578");
user.setAddress("广东省深圳市南山区....");
user.setIdCard("4333333333334334333");
return user;
}
}
json
{
"realName": "小*博客",
"address": "广东省****市南山区..****",
"phoneNumber": "176****6578",
"idCard": "4333****34333"
}
4、日志脱敏
1、引入log4j
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions><!-- 去掉springboot默认配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--使用log4j2替换 LogBack-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
2、新建log4j2.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
<!-- 定义日志存放目录 -->
<properties>
<property name="logPath">logs</property>
<property name="PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%t-%L] %-5level %logger{36} %L %M - %msg%xEx%n</property>
<!--"%-d{yyyy-MM-dd HH:mm:ss} [%thread] %m%n"-->
</properties>
<!--先定义所有的appender(输出器) -->
<Appenders>
<!--输出到控制台 -->
<Console name="ConsoleLog" target="SYSTEM_OUT">
<!--只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="TRACE" onMatch="ACCEPT" onMismatch="DENY" />
<!--输出日志的格式,引用自定义模板 PATTERN -->
<CustomPatternLayout pattern="${PATTERN}" />
</Console>
<RollingFile name="APPLog" fileName="${logPath}/log_app.log" filePattern="${logPath}/log_%d{yyyy-MM-dd}.log">
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY" />
<!--<CustomPatternLayout pattern="%-d{yyyy-MM-dd HH:mm:ss} [%thread] %m%n"/>-->
<CustomPatternLayout pattern="${PATTERN}" />
<Policies>
<TimeBasedTriggeringPolicy modulate="true" interval="1"/>
</Policies>
<CronTriggeringPolicy schedule="0 0 5 * * ? "/>
<DefaultRolloverStrategy>
<Delete basePath="${logPath}" maxDepth="1">
<IfFileName glob="log_app_*.log" />
<!--删除15天前的文件-->
<IfLastModified age="15d" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
<!-- 把error等级记录到文件 一般不用 -->
<File name="ERRORLog" fileName="${logPath}/error.log">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY" />
<CustomPatternLayout pattern="${PATTERN}" />
</File>
</Appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
<Loggers>
<!--建立一个默认的Root的logger,记录大于level高于warn的信息,如果这里的level高于Appenders中的,则Appenders中也是以此等级为起点,比如,这里level="fatal",则Appenders中只出现fatal信息 -->
<!-- 生产环境level>=warn -->
<Root level="debug">
<!-- 输出器,可选上面定义的任何项组合,或全选,做到可随意定制 -->
<appender-ref ref="ConsoleLog" />
<appender-ref ref="ERRORLog" />
<appender-ref ref="APPLog" />
</Root>
<!-- 第三方日志系统 -->
<!--过滤掉spring和mybatis的一些无用的DEBUG信息,也可以在spring boot 的logging.level.org.springframework=FATAL设置-->
<logger name="org.springframework" level="DEBUG"></logger>
<logger name="java.sql.Connection" level="DEBUG"></logger>
<logger name="java.sql.Statement" level="DEBUG"></logger>
<logger name="java.sql.PreparedStatement" level="DEBUG"></logger>
</Loggers>
</Configuration>
3、新建日志规则
java
package com.xx.log4j;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:56、
* 现在拦截加密的日志有三类:
* 1,身份证
* 2,姓名
* 3,身份证号
* 加密的规则后续可以优化在配置文件中
*/
public class Log4j2Rule {
/**
* 正则匹配 关键词 类别
*/
public static Map<String, String> regularMap = new HashMap<>();
/**
* TODO 可配置
* 此项可以后期放在配置项中
*/
public static final String USER_NAME_STR = "Name,name,联系人,姓名";
public static final String USER_IDCARD_STR = "empCard,idCard,身份证,证件号";
public static final String USER_PHONE_STR = "mobile,Phone,phone,电话,手机";
/**
* 正则匹配,自己根据业务要求自定义
*/
private static String IDCARD_REGEXP = "(\\d{17}[0-9Xx]|\\d{14}[0-9Xx])";
private static String USERNAME_REGEXP = "[\\u4e00-\\u9fa5]{2,4}";
private static String PHONE_REGEXP = "(?<!\\d)(?:(?:1[3456789]\\d{9})|(?:861[356789]\\d{9}))(?!\\d)";
static {
regularMap.put(USER_NAME_STR, USERNAME_REGEXP);
regularMap.put(USER_IDCARD_STR, IDCARD_REGEXP);
regularMap.put(USER_PHONE_STR, PHONE_REGEXP);
}
}
4、新建日志插件
java
package com.xx.log4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:55
*/
@Plugin(name = "CustomPatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public class CustomPatternLayout extends AbstractStringLayout {
public final static Logger logger = LoggerFactory.getLogger(CustomPatternLayout.class);
private PatternLayout patternLayout;
protected CustomPatternLayout(Charset charset, String pattern) {
super(charset);
patternLayout = PatternLayout.newBuilder().withPattern(pattern).build();
initRule();
}
/**
* 要匹配的正则表达式map
*/
private static Map<String, Pattern> REG_PATTERN_MAP = new HashMap<>();
private static Map<String, String> KEY_REG_MAP = new HashMap<>();
private void initRule() {
try {
if (MapUtils.isEmpty(Log4j2Rule.regularMap)) {
return;
}
Log4j2Rule.regularMap.forEach((a, b) -> {
if (StringUtils.isNotBlank(a)) {
Map<String, String> collect = Arrays.stream(a.split(",")).collect(Collectors.toMap(c -> c, w -> b, (key1, key2) -> key1));
KEY_REG_MAP.putAll(collect);
}
Pattern compile = Pattern.compile(b);
REG_PATTERN_MAP.put(b, compile);
});
} catch (Exception e) {
logger.info(">>>>>> 初始化日志脱敏规则失败 ERROR:{}", e);
}
}
/**
* 处理日志信息,进行脱敏
* 1.判断配置文件中是否已经配置需要脱敏字段
* 2.判断内容是否有需要脱敏的敏感信息
* 2.1 没有需要脱敏信息直接返回
* 2.2 处理: 身份证 ,姓名,手机号敏感信息
*/
public String hideMarkLog(String logStr) {
try {
//1.判断配置文件中是否已经配置需要脱敏字段
if (StringUtils.isBlank(logStr) || MapUtils.isEmpty(KEY_REG_MAP) || MapUtils.isEmpty(REG_PATTERN_MAP)) {
return logStr;
}
//2.判断内容是否有需要脱敏的敏感信息
Set<String> charKeys = KEY_REG_MAP.keySet();
for (String key : charKeys) {
if (logStr.contains(key)) {
String regExp = KEY_REG_MAP.get(key);
logStr = matchingAndEncrypt(logStr, regExp, key);
}
}
return logStr;
} catch (Exception e) {
logger.info(">>>>>>>>> 脱敏处理异常 ERROR:{}", e);
//如果抛出异常为了不影响流程,直接返回原信息
return logStr;
}
}
/**
* 正则匹配对应的对象。
*
* @param msg
* @param regExp
* @return
*/
private static String matchingAndEncrypt(String msg, String regExp, String key) {
Pattern pattern = REG_PATTERN_MAP.get(regExp);
if (pattern == null) {
logger.info(">>> logger 没有匹配到对应的正则表达式 ");
return msg;
}
Matcher matcher = pattern.matcher(msg);
int length = key.length() + 5;
boolean contains = Log4j2Rule.USER_NAME_STR.contains(key);
String hiddenStr = "";
while (matcher.find()) {
String originStr = matcher.group();
if (contains) {
// 计算关键词和需要脱敏词的距离小于5。
int i = msg.indexOf(originStr);
if (i < 0) {
continue;
}
int span = i - length;
int startIndex = span >= 0 ? span : 0;
String substring = msg.substring(startIndex, i);
if (StringUtils.isBlank(substring) || !substring.contains(key)) {
continue;
}
hiddenStr = hideMarkStr(originStr);
msg = msg.replace(originStr, hiddenStr);
} else {
hiddenStr = hideMarkStr(originStr);
msg = msg.replace(originStr, hiddenStr);
}
}
return msg;
}
/**
* 标记敏感文字规则
*
* @param needHideMark
* @return
*/
private static String hideMarkStr(String needHideMark) {
if (StringUtils.isBlank(needHideMark)) {
return "";
}
int startSize = 0, endSize = 0, mark = 0, length = needHideMark.length();
StringBuffer hideRegBuffer = new StringBuffer("(\\S{");
StringBuffer replaceSb = new StringBuffer("$1");
if (length > 4) {
int i = length / 3;
startSize = i;
endSize = i;
} else {
startSize = 1;
endSize = 0;
}
mark = length - startSize - endSize;
for (int i = 0; i < mark; i++) {
replaceSb.append("*");
}
hideRegBuffer.append(startSize).append("})\\S*(\\S{").append(endSize).append("})");
replaceSb.append("$2");
needHideMark = needHideMark.replaceAll(hideRegBuffer.toString(), replaceSb.toString());
return needHideMark;
}
/**
* 创建插件
*/
@PluginFactory
public static Layout createLayout(@PluginAttribute(value = "pattern") final String pattern,
@PluginAttribute(value = "charset") final Charset charset) {
return new CustomPatternLayout(charset, pattern);
}
@Override
public String toSerializable(LogEvent event) {
return hideMarkLog(patternLayout.toSerializable(event));
}
}
5、新建接口测试
java
package com.xx.controller;
import com.xx.model.Person;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: xueqimiao
* @Date: 2023/1/30 13:37
*/
@RestController
@Slf4j
public class TestController {
@GetMapping("/test")
public Person test() {
Person user = new Person();
user.setRealName("小薛博客");
user.setPhoneNumber("17683456578");
user.setAddress("广东省深圳市南山区....");
user.setIdCard("4333333333334334333");
log.info("----------->"+user.toString());
return user;
}
}
java
2023-01-30 13:57:13.978 [] [http-nio-8080-exec-1-23] INFO com.xx.controller.TestController 23 test - ----------->Person(realName=小***, address=广东省深圳市南山区...., phoneNumber=176*****578, idCard=433333******4334333)