sensitive敏感信息脱敏

有一些敏感信息,比如手机号、身份证号等,需要在日志,以及页面展示的时候进行脱敏。避免数据泄露。

步骤

1.选择脱敏框架

sensitive框架:https://github.com/houbb/sensitive

2.maven引入

引入核心脱敏包

<dependency>
  <groupId>com.github.houbb</groupId>
  <artifactId>sensitive-logback</artifactId>
  <version>1.7.0</version>
</dependency>

引入 logback 依赖包

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>${logback.version}</version>
</dependency>

3.logback.xml 配置

下面是logback模版:

<configuration>
  <!-- 基于 converter -->
  <conversionRule conversionWord="sensitive" converterClass="com.github.houbb.sensitive.logback.converter.SensitiveLogbackConverter" />
  <!-- 使用 converter -->
  <appender name="STDOUTConverter" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %sensitive%n</pattern>
    </encoder>
  </appender>

  <!-- 使用 layout -->
  <appender name="STDOUTLayout" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="com.github.houbb.sensitive.logback.layout.SensitiveLogbackLayout">
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </layout>
  </appender>

  <!-- 设置根日志级别为DEBUG,并将日志输出到控制台 -->
  <root level="DEBUG">
    <appender-ref ref="STDOUTConverter"/>
    <appender-ref ref="STDOUTLayout"/>
  </root>
</configuration>

这里共计支持 Converter 和 Layout 两种模式,任选一个即可。

建议使用 SensitiveLogbackConverter,脱敏日志内容。

配置项目里面的logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <springProperty scope="context" name="app.name" source="spring.application.name"/>
    <!-- 基于 converter -->
    <conversionRule conversionWord="sensitive" converterClass="com.github.houbb.sensitive.logback.converter.SensitiveLogbackConverter" />

    <property name="APP_NAME" value="${app.name}"/>
    <property name="LOG_PATH" value="${user.home}/${APP_NAME}/logs"/>
    <property name="LOG_FILE" value="${LOG_PATH}/application.log"/>
    <property name="FILE_LOG_PATTERN" value="%d %-5level [%thread %logger - %sensitive%n"/>

    <appender name="APPLICATION"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxHistory>7</maxHistory>
            <maxFileSize>50MB</maxFileSize>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="APPLICATION"/>
    </root>
</configuration>

其中需要关注的是converter引入

<conversionRule conversionWord="sensitive" converterClass="com.github.houbb.sensitive.logback.converter.SensitiveLogbackConverter" />

还有日志格式配置

<property name="FILE_LOG_PATTERN" value="%d %-5level [%thread %logger - %sensitive%n"/>

4.日志效果

脱密效果如下:

2025-09-26 09:55:50,260 INFO  [http-nio-8085-exec-10 cn.mifu.nft.turbo.rpc.facade.FacadeAspect - start to execute , method = auth , args = [{"idCard":"2****************2|EDB6003794DD4ED72CFA72F54CF4280D","realName":"张三","userId":9}

5.脱敏前台展示字段

脱敏注解举例

public class Example {

    @SensitiveStrategyChineseName
    private String username;

    @SensitiveStrategyPassword
    private String password;

    @SensitiveStrategyPassport
    private String passport;

    @SensitiveStrategyIdNo
    private String idNo;

    @SensitiveStrategyCardId
    private String bandCardId;

    @SensitiveStrategyPhone
    private String phone;

    @SensitiveStrategyEmail
    private String email;

    @SensitiveStrategyAddress
    private String address;

    @SensitiveStrategyBirthday
    private String birthday;

    @SensitiveStrategyGps
    private String gps;

    @SensitiveStrategyIp
    private String ip;

    @SensitiveStrategyMaskAll
    private String maskAll;

    @SensitiveStrategyMaskHalf
    private String maskHalf;

    @SensitiveStrategyMaskRange
    private String maskRange;
}

具体操作

@Setter
@Getter
@TableName("users")
public class User extends BaseEntity {
    /**
     * 手机号
     */
    @SensitiveStrategyPhone
    private String telephone;
}

增加一个拦截

当运行getUserInfo的时候

@GetMapping("/getUserInfo")
    public Result<UserInfo> getUserInfo() {
        String userId = (String) StpUtil.getLoginId();
        UserQueryRequest request = new UserQueryRequest();
        request.setUserId(Long.valueOf(userId));
        User user = userService.findById(Long.valueOf(userId));

        if (user == null) {
            throw new UserException(USER_NOT_EXIST);
        }
   
        return Result.success(UserConvertor.INSTANCE.mapToVo(user));
    }

对Result类型的返回结果进行拦截

@ControllerAdvice
public class SensitiveResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 只对特定类型的返回值执行处理逻辑,这里可以根据需要调整判断条件
        return Result.class.isAssignableFrom(returnType.getParameterType());
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 如果返回的对象是UserInfo,进行脱敏处理
        if (body instanceof Result && ((Result<?>) body).getData() instanceof UserInfo) {
            Result<UserInfo> result = (Result<UserInfo>) body;
            UserInfo userInfo = result.getData();
            User user=UserConvertor.INSTANCE.mapToEntity(userInfo);
            user = SensitiveUtil.desCopy(user);
            result.setData(UserConvertor.INSTANCE.mapToVo(user));
            return result;
        }
        return body;
    }

然后判断返回对象是UserInfo,通过 SensitiveUtil.desCopy(user)进行脱敏

5.个性化配置

用户可以在应用 resources 下通过 chars-scan-config.properties 配置文件指定。

默认配置

SensitivePatternLayout 配置默认为:

chars.scan.prefix=::,,'"‘“=| +()()
chars.scan.scanList=1,2,3,4,9
chars.scan.replaceList=1,2,3,4,9
chars.scan.defaultReplace=12
chars.scan.replaceHash=md5
chars.scan.whiteList=""

属性说明

SensitivePatternLayout 策略的属性说明。

属性 说明 默认值 备注
prefix 需要脱敏信息的匹配前缀 ::,,'"‘“= +()() 和英文竖线 降低误判率
replaceHash 哈希策略模式 md5 支持 md5/none 两种模式
scanList 敏感扫描策略列表 1,2,3,4 1~10 内置的10种敏感信息扫描策略,多个用逗号隔开
replaceList 敏感替换策略列表 1,2,3,4 1~10 内置的10种敏感信息替换策略,多个用逗号隔开
defaultReplace 敏感替换默认策略 12 1~13 内置的13种敏感信息替换策略,指定一个。当列表没有匹配时,默认使用这个
whiteList 白名单 `` 希望跳过处理的白名单信息

其中 1-13 的内置策略说明如下:

策略标识 说明
1 手机号
2 身份证
3 银行卡
4 邮箱
5 中国人名
6 出生日期
7 GPS
8 IPV4
9 地址
10 护照
11 匹配任意不掩盖
12 匹配任意半掩盖
13 匹配任意全掩盖
m1 数字类合并操作(m1:1&2&3) 性能更好
m3 拓展类合并操作(m3:4&5&9) 性能更好