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 -> phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
ID_CARD(id -> id.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1**********$2")),
BANK_CARD(card -> card.replaceAll("(\\d{4})\\d+(\\d{4})", "$1****$2")),
EMAIL(email -> email.replaceAll("(\\w{3})\\w+(@\\w+)", "$1***$2")),
NAME(name -> name.replaceAll("^(.{1}).*$", "$1*"));
private final Function<String, String> masker;
SensitiveType(Function<String, String> 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<?> 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 项目提供了一套轻量、通用的数据脱敏方案。核心优势在于:业务代码零侵入,只需在字段上添加注解即可;脱敏规则集中管理,便于统一维护和扩展。该方案适用于接口返回、消息推送等展示场景,能够有效降低敏感信息泄露风险。