描述

脱敏模块用于在数据序列化输出到前端时,对敏感信息(如手机号、身份证号、银行卡号、邮箱等)进行自动脱敏处理,防止敏感数据泄露。

核心特性: * 基于 Jackson 序列化实现,同时兼容 Spring MVC(Servlet)和 Spring WebFlux(Reactive)两种 Web 技术栈。 * 提供 @RegexSensitive、@SlideSensitive、@CustomSensitive 三种脱敏注解,覆盖正则替换、滑动窗口、自定义处理器等场景。 * 内置常用的脱敏策略(手机号、身份证号、银行卡号、邮箱等),开箱即用。 * 支持自定义脱敏处理器,可注入 Spring Bean,实现数据库查询、远程调用等复杂脱敏逻辑。 * 支持多注解叠加,按 order 顺序链式执行。

快速开始

引入依赖后,只需在需要脱敏的字段上添加对应注解即可自动生效,无需额外配置:

@Data
public class UserVo {

    @SlideSensitive(type = SlideSensitiveType.PHONE_NUMBER)
    private String phone;

    @SlideSensitive(type = SlideSensitiveType.ID_CARD_NO)
    private String idCardNo;

    @RegexSensitive(type = RegexSensitiveType.EMAIL)
    private String email;
}

控制器返回该对象时,Jackson 序列化会自动拦截并执行脱敏逻辑,对业务代码完全透明。

仓库坐标

Maven

<dependency>
    <groupId>com.luamas.thinkbridge</groupId>
    <artifactId>sensitive-spring-boot-starter</artifactId>
</dependency>

Gradle

implementation("com.luamas.thinkbridge:sensitive-spring-boot-starter")

脱敏注解

框架提供了三种脱敏注解,分别对应不同的脱敏策略:

@RegexSensitive

基于正则表达式的脱敏,通过匹配和替换规则实现。

属性

默认值

说明

type

无(必填)

脱敏类型,见 RegexSensitiveType 枚举

regex

"^[\s\S]*$"

匹配的正则表达式,仅 type=CUSTOM 时生效

replacement

""

替换规则,仅 type=CUSTOM 时生效

order

0

多注解叠加时的执行顺序,值越小越先执行

RegexSensitiveType 枚举:

枚举值

正则

替换规则

说明

CUSTOM

^[\s\S]*$

自定义类型

EMAIL

(^.)(@.$)

$1**$2

邮箱脱敏,如 1**@qq.com

ENCRYPTED_PASSWORD

(.{3}).*(.{2}$)

$1**$2

对称密文密码脱敏

// 邮箱脱敏
@RegexSensitive(type = RegexSensitiveType.EMAIL)
private String email;

// 自定义正则脱敏
@RegexSensitive(type = RegexSensitiveType.CUSTOM, regex = "(.{2}).(.{2}$)", replacement = "$1***$2")
private String customField;

@SlideSensitive

基于滑动窗口的脱敏,保留左右明文,中间用掩码字符替换。

属性

默认值

说明

type

无(必填)

脱敏类型,见 SlideSensitiveType 枚举

leftPlainTextLen

0

左边保留的明文数,仅 type=CUSTOM 时生效

rightPlainTextLen

0

右边保留的明文数,仅 type=CUSTOM 时生效

maskString

"*"

掩码字符,仅 type=CUSTOM 时生效

order

0

多注解叠加时的执行顺序,值越小越先执行

SlideSensitiveType 枚举:

枚举值

左明文

右明文

掩码

说明

CUSTOM

0

0

*

自定义类型

ALL_ASTERISK

0

0

*

全部替换为 *

BANK_CARD_NO

6

4

*

银行卡号,如 330150**1234

ID_CARD_NO

6

4

*

身份证号,如 655356*1234

PHONE_NUMBER

3

2

*

手机号,如 138**56

// 手机号脱敏
@SlideSensitive(type = SlideSensitiveType.PHONE_NUMBER)
private String phone;

// 银行卡号脱敏
@SlideSensitive(type = SlideSensitiveType.BANK_CARD_NO)
private String bankCardNo;

// 自定义滑动脱敏
@SlideSensitive(type = SlideSensitiveType.CUSTOM, leftPlainTextLen = 2, rightPlainTextLen = 2, maskString = "#")
private String customField;

@CustomSensitive

使用自定义脱敏处理器,实现 CustomDesensitizationHandler 接口。

属性

默认值

说明

handler

无(必填)

自定义脱敏处理器类

args

{}

传递给处理器的构造参数

ignoreEmpty

true

空字符串是否跳过脱敏,为 true 时空字符串原样输出不进入处理器

condition

""

条件脱敏 SpEL 表达式(预留属性,暂未实现)

order

0

多注解叠加时的执行顺序,值越小越先执行

@CustomSensitive(handler = TestDesensitizationHandler.class)
private String field;

高级特性

多注解叠加

一个字段可以同时标注多个脱敏注解,框架会按 order 属性升序依次执行,前一个注解的脱敏结果作为后一个注解的输入:

// 先滑动脱敏保留前3后4,再正则脱敏
@SlideSensitive(type = SlideSensitiveType.CUSTOM, leftPlainTextLen = 3, rightPlainTextLen = 4, order = 1)
@RegexSensitive(type = RegexSensitiveType.CUSTOM, regex = "(\\d{4})\\d{4}(\\d{4})", replacement = "$1**$2", order = 2)
private String phone;
order 默认值为 0,未指定 order 属性的注解按声明顺序执行。建议在叠加场景下显式指定 order 以保证执行顺序明确。

空值跳过

@CustomSensitive 默认开启 ignoreEmpty = true,空字符串不会进入脱敏处理器,避免产生 这类无意义结果。如需对空字符串也进行脱敏,可关闭此选项:

@CustomSensitive(handler = TestDesensitizationHandler.class, ignoreEmpty = false)
private String field;

Spring Bean 注入

自定义脱敏处理器支持声明为 Spring Bean,可注入任意依赖(如数据库访问层、配置、远程服务)。框架会优先从 Spring 容器获取处理器实例,获取不到时才通过反射创建。

@Component
public class DbMaskHandler implements CustomDesensitizationHandler {

    @Autowired
    private MaskRuleMapper maskRuleMapper;

    @Override
    public String handle(String origin, String[] args) {
        // 从数据库查询脱敏规则
        MaskRule rule = maskRuleMapper.findByField(args[0]);
        return origin.replaceAll(rule.getRegex(), rule.getReplacement());
    }
}

然后直接在注解中引用该处理器类即可,无需手动注册:

@CustomSensitive(handler = DbMaskHandler.class, args = {"user_phone"})
private String phone;
处理器类必须实现 CustomDesensitizationHandler 接口。如果声明为 Spring Bean,框架会自动从容器获取;如果未声明为 Bean,框架会通过反射调用无参构造方法创建实例。

自定义复合注解

当项目中多个字段需要使用相同的脱敏策略组合时,可以基于 @CustomSensitive 创建自定义复合注解。核心思路是将 @CustomSensitive 作为元注解标注在自定义注解上,并实现一个 CustomDesensitizationHandler 封装复合脱敏逻辑。

典型的应用场景包括: * 将 @SlideSensitive(type = SlideSensitiveType.PHONE_NUMBER) 封装为 @PhoneSensitive * 将 @SlideSensitive(type = SlideSensitiveType.ID_CARD_NO) 封装为 @IdCardSensitive * 将多个脱敏规则组合为一个复合注解,如同时进行滑动脱敏和正则脱敏

下面以封装手机号脱敏为例,展示完整的实现步骤:

1. 实现 CustomDesensitizationHandler

处理器内部复用内置的 SlideDesensitizationHandler,封装手机号脱敏逻辑:

@Component
public class PhoneSensitiveHandler implements CustomDesensitizationHandler {

    @Override
    public String handle(String origin, String[] args) {
        SlideDesensitizationHandler handler = DesensitizationHandlerHolder
                .getSlideDesensitizationHandler();
        return handler.handle(origin, SlideSensitiveType.PHONE_NUMBER);
    }
}

2. 定义复合注解

以 @CustomSensitive 作为元注解,指定处理器类:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@CustomSensitive(handler = PhoneSensitiveHandler.class)
public @interface PhoneSensitive {
}

3. 使用复合注解

在实体类字段上直接使用自定义注解,与使用内置注解体验一致:

@Data
public class UserVo {

    @PhoneSensitive
    private String phone;
}

复合脱敏示例

以下示例展示如何创建一个组合了滑动脱敏和正则脱敏的复合注解,实现对手机号的"保留前3后4 + 中间四位掩码"双重脱敏:

@Component
public class CompositePhoneHandler implements CustomDesensitizationHandler {

    @Override
    public String handle(String origin, String[] args) {
        SlideDesensitizationHandler slideHandler = DesensitizationHandlerHolder
                .getSlideDesensitizationHandler();
        RegexDesensitizationHandler regexHandler = DesensitizationHandlerHolder
                .getRegexDesensitizationHandler();
        // 第一步:滑动脱敏,保留前3后4
        String step1 = slideHandler.handle(origin, 3, 4, "");
        // 第二步:正则脱敏,将中间四位数字替换为 *
        return regexHandler.handle(step1, "(\\d{4})\\d{4}(\\d{4})", "$1**$2");
    }
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@CustomSensitive(handler = CompositePhoneHandler.class)
public @interface CompositePhoneSensitive {
}
自定义复合注解的核心优势在于将脱敏规则集中管理。当规则需要调整时,只需修改 Handler 实现,无需逐个修改实体类中的注解配置。同时,语义化的注解名也让代码更具可读性。Handler 声明为 @Component 后,框架会自动从 Spring 容器获取实例,可注入任意依赖。

示例

下面展示一个完整的脱敏使用示例,包含脱敏对象定义、自定义处理器实现和控制器接口。

脱敏对象

package com.luamas.thinkbridge.integration.demo.sensitive.vo;

import com.luamas.thinkbridge.common.sensitive.annotation.CustomSensitive;
import com.luamas.thinkbridge.common.sensitive.annotation.RegexSensitive;
import com.luamas.thinkbridge.common.sensitive.annotation.SlideSensitive;
import com.luamas.thinkbridge.common.sensitive.enums.RegexSensitiveType;
import com.luamas.thinkbridge.common.sensitive.enums.SlideSensitiveType;
import com.luamas.thinkbridge.integration.demo.sensitive.handler.TestDesensitizationHandler;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
@Schema
public class UserVo {

    /
     * 用户名,不脱敏
     /
    @Schema(title = "用户名", example = "xiaoming")
    private String username;

    /
     * 密码脱敏
     */
    @Schema(title = "密码", example = "admina123456")
    @RegexSensitive(type = RegexSensitiveType.ENCRYPTED_PASSWORD)
    private String password;

    /
     * 邮件
     */
    @Schema(title = "邮箱", example = "luamas@gmail.com")
    @RegexSensitive(type = RegexSensitiveType.EMAIL)
    private String email;

    /
     * 手机号
     */
    @Schema(title = "手机号", example = "13800138000")
    @SlideSensitive(type = SlideSensitiveType.PHONE_NUMBER)
    private String phoneNumber;

    /*
     * 测试自定义脱敏
     */
    @Schema(title = "自定义脱敏测试字段", example = "这是测试属性")
    @CustomSensitive(handler = TestDesensitizationHandler.class)
    private String testField;

}

自定义脱敏处理器

package com.luamas.thinkbridge.integration.demo.sensitive.handler;

import com.luamas.thinkbridge.common.sensitive.handler.CustomDesensitizationHandler;

public class TestDesensitizationHandler implements CustomDesensitizationHandler {

    @Override
    public String handle(String s, String... args) {
        return "TEST-" + s;
    }

}

MVC 控制器

package com.luamas.thinkbridge.integration.demo.sensitive.controller;

import com.luamas.thinkbridge.integration.demo.sensitive.vo.UserVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Profile("sensitive")
@Tag(name = "脱敏")
@RestController
@RequestMapping("sensitive")
public class SensitiveController {

    @Operation(summary = "查看脱敏效果",
            description = "返回包含多种脱敏类型的用户信息,演示正则脱敏、滑动脱敏和自定义脱敏")
    @GetMapping
    public UserVo sensitive(
            @Parameter(description = "用户名", example = "xiaoming") String username,
            @Parameter(description = "密码", example = "admina123456") String password,
            @Parameter(description = "邮箱", example = "luamas@gmail.com") String email,
            @Parameter(description = "手机号", example = "13800138000") String phoneNumber,
            @Parameter(description = "自定义脱敏字段", example = "这是测试属性") String testField) {
        return new UserVo()
                .setUsername(username)
                .setPassword(password)
                .setEmail(email)
                .setPhoneNumber(phoneNumber)
                .setTestField(testField);
    }
}