Spring Boot 实现数据脱敏:自定义注解 + Jackson 序列化器

1. 引言

在业务系统中,手机号、身份证号、银行卡号、邮箱等敏感信息经常出现在接口返回结果中。如果直接以明文形式返回给前端,一旦日志泄露或接口被非法调用,就会造成严重的数据安全风险。数据脱敏(Data Masking)就是在不改变数据存储的前提下,对展示层的数据进行遮蔽处理,例如把手机号 138****1234 中的中间四位替换为星号。

本文介绍一种基于 Spring Boot 的通用脱敏方案:通过自定义注解标记需要脱敏的字段,再借助 Jackson 序列化器在对象转 JSON 时自动完成脱敏。该方案侵入性小、扩展性强,适合在现有项目中快速落地。

2. 方案设计

整体思路分为三步:

  • 定义脱敏类型枚举:声明手机号、身份证、银行卡、邮箱、姓名等常见脱敏规则。
  • 编写自定义注解:在实体类字段上标注脱敏类型,声明该字段需要脱敏。
  • 实现 Jackson 序列化器:通过自定义序列化器读取注解,在序列化阶段对字段值做遮蔽处理。

为了让序列化器能够感知字段上的注解,需要借助 Jackson 的 ContextualSerializer 接口。该接口允许序列化器在创建时获取当前字段的上下文信息,从而拿到注解中配置的脱敏类型。

3. 代码实现

3.1 引入依赖

项目基于 Spring Boot 2.x,核心依赖如下:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

3.2 定义脱敏类型枚举

枚举中每个类型对应一种脱敏规则,通过函数式接口统一处理,便于后续扩展新的脱敏类型。

java 复制代码
import java.util.function.Function;
public enum SensitiveType {
MOBILE(phone -&gt; phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
ID_CARD(id -&gt; id.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1**********$2")),
BANK_CARD(card -&gt; card.replaceAll("(\\d{4})\\d+(\\d{4})", "$1****$2")),
EMAIL(email -&gt; email.replaceAll("(\\w{3})\\w+(@\\w+)", "$1***$2")),
NAME(name -&gt; name.replaceAll("^(.{1}).*$", "$1*"));
private final Function&lt;String, String&gt; masker;
SensitiveType(Function&lt;String, String&gt; masker) {
this.masker = masker;
}
public String mask(String value) {
return value == null ? null : masker.apply(value);
}
}

3.3 自定义脱敏注解

注解用于标记实体类中需要脱敏的字段,通过 type 属性指定脱敏规则。

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {
SensitiveType type();
}

3.4 实现 Jackson 序列化器

自定义序列化器继承 JsonSerializer 并实现 ContextualSerializer,在 createContextual 方法中读取字段注解,绑定对应的脱敏类型。

java 复制代码
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 java.io.IOException;
public class SensitiveSerializer extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveType type;
public SensitiveSerializer() {
}
public SensitiveSerializer(SensitiveType type) {
this.type = type;
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(type.mask(value));
}
@Override
public JsonSerializer&lt;?&gt; createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (annotation != null) {
return new SensitiveSerializer(annotation.type());
}
return prov.findValueSerializer(property.getType(), property);
}
}

3.5 在实体类中使用注解

在需要脱敏的字段上添加 @Sensitive 注解即可,业务代码无需任何改动。

java 复制代码
public class UserVO {
private Long id;
@Sensitive(type = SensitiveType.NAME)
private String name;
@Sensitive(type = SensitiveType.MOBILE)
private String mobile;
@Sensitive(type = SensitiveType.ID_CARD)
private String idCard;
@Sensitive(type = SensitiveType.EMAIL)
private String email;
// 省略 getter / setter
}

4. 测试验证

编写一个简单的 Controller 返回用户对象,观察序列化结果。

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/{id}")
public UserVO getUser(@PathVariable Long id) {
    UserVO vo = new UserVO();
    vo.setId(id);
    vo.setName("张三丰");
    vo.setMobile("13812345678");
    vo.setIdCard("110101199003071234");
    vo.setEmail("zhangsan@example.com");
    return vo;
}
}

请求 GET /user/1 后,返回的 JSON 如下:

json 复制代码
{
  "id": 1,
  "name": "张*",
  "mobile": "138****5678",
  "idCard": "1101**********1234",
  "email": "zha***@example.com"
}

可以看到,敏感字段在序列化阶段被自动遮蔽,而普通字段保持原样。

5. 扩展与注意事项

  • 自定义脱敏规则 :在 SensitiveType 枚举中新增类型并实现对应的遮蔽逻辑即可,无需改动序列化器。
  • 嵌套对象:该方案对嵌套对象同样生效,只要嵌套对象中的字段标注了注解,Jackson 在递归序列化时会自动应用。
  • 集合字段 :对于 List<UserVO> 等集合类型,序列化器同样会作用于集合内的每个元素。
  • 日志脱敏:本方案只作用于 JSON 序列化输出,如果项目使用 Logback 打印日志,建议结合日志脱敏组件(如 Logstash Logback Encoder 的脱敏功能)一起使用。
  • 性能影响:脱敏逻辑为简单的正则替换,对序列化性能影响极小,可忽略不计。

6. 总结

本文通过自定义注解和 Jackson 序列化器,为 Spring Boot 项目提供了一套轻量、通用的数据脱敏方案。核心优势在于:业务代码零侵入,只需在字段上添加注解即可;脱敏规则集中管理,便于统一维护和扩展。该方案适用于接口返回、消息推送等展示场景,能够有效降低敏感信息泄露风险。

相关推荐
vx-Biye_Design5 小时前
SSM伴侣动物伴护星小程序06330-计算机课程设计、毕业设计
spring boot·后端·elasticsearch·小程序·架构·课程设计·idea
2分钟速写快排7 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
面向Google编程7 小时前
Apache Iceberg Variant 类型:v3 半结构化数据不再「二选一」
大数据·后端
lhldsg8 小时前
全民健身解决方案小程序开发:从0到1的技术实战
java·数据库·需求分析
jason成都8 小时前
Spring WebFlux 适配达梦新方案|dm‑r2dbc:Netty 异步传输的实验性 R2DBC 驱动
java·后端·spring
泡海椒8 小时前
内置SPI函数库详解:JQuick-Java Builtin工具类实战用法
java·开发语言·python
辰烨chenye8 小时前
LeetCode Hot 100 题解 · 二分篇
java·算法·leetcode
小羊没烦恼!9 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰9 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端