在日常的后端开发中,用户手机号等敏感信息的脱敏处理是数据安全合规的基本要求。直接在业务代码中硬编码脱敏逻辑会导致代码冗余、维护成本高,本文将分享一种基于Jackson自定义注解的优雅实现方案,只需一个注解即可完成手机号字段的自动脱敏。
在接口返回用户数据时,手机号这类敏感信息不能明文展示,通常需要按照138****5678的格式进行脱敏。我们希望:
首先确保项目中引入了必要的依赖(以Maven为例):
xml<!-- Jackson核心依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Hutool工具类库 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
</dependency>
创建自定义注解PhoneDesensitization,用于标记需要脱敏的手机号字段:
javapackage com.nsw.server.common.annotation;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.nsw.server.common.config.PhoneDesensitizationSerializer;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 手机号脱敏注解
* 标注在需要脱敏的phone字段上
*
* @author weizhenwang
*/
// 注解作用目标:字段
@Target({ElementType.FIELD})
// 注解保留策略:运行时生效(反射可获取)
@Retention(RetentionPolicy.RUNTIME)
// 生成文档时包含该注解
@Documented
// 嵌套Jackson注解的标记,避免注解冲突
@JacksonAnnotationsInside
// 指定序列化器:使用自定义的PhoneDesensitizationSerializer处理字段
@JsonSerialize(using = PhoneDesensitizationSerializer.class)
public @interface PhoneDesensitization {
}
注解关键属性说明:
@Target(ElementType.FIELD):限定注解只能用在类的字段上@Retention(RetentionPolicy.RUNTIME):注解在运行时保留,Jackson序列化时能通过反射识别@JacksonAnnotationsInside:Jackson的注解,用于嵌套自定义注解,防止注解失效@JsonSerialize:指定该字段序列化时使用的自定义序列化器创建PhoneDesensitizationSerializer类,继承Jackson的JsonSerializer,实现具体的脱敏逻辑:
javapackage com.nsw.server.common.config;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
* 手机号脱敏序列化器
*/
public class PhoneDesensitizationSerializer extends JsonSerializer<String> {
@Override
public void serialize(String phone, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 1. 空值处理:空字符串/Null直接返回,避免空指针
if (StrUtil.isBlank(phone)) {
gen.writeString(phone);
return;
}
// 2. 手机号格式校验:使用Hutool的Validator工具类
if (Validator.isMobile(phone)) {
// 3. 格式正确则脱敏:Hutool的DesensitizedUtil实现138****5678格式
String desensitizedPhone = DesensitizedUtil.mobilePhone(phone);
gen.writeString(desensitizedPhone);
} else {
// 4. 格式错误则原样返回,保证数据兼容性
gen.writeString(phone);
}
}
}
核心逻辑说明:
StrUtil.isBlank()判断空值,避免后续逻辑出现NPEValidator.isMobile()支持中国大陆11位手机号格式校验DesensitizedUtil.mobilePhone()内置了成熟的手机号脱敏逻辑(保留前3位和后4位,中间4位替换为*)在需要脱敏的手机号字段上添加@PhoneDesensitization注解:
javapackage com.nsw.server.entity;
import com.nsw.server.common.annotation.PhoneDesensitization;
import lombok.Data;
@Data
public class User {
/**
* 用户ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 手机号(脱敏展示)
*/
@PhoneDesensitization
private String phone;
/**
* 邮箱
*/
private String email;
}
编写测试接口:
javapackage com.nsw.server.controller;
import com.nsw.server.entity.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/info")
public User getUserInfo() {
User user = new User();
user.setId(1L);
user.setUsername("张三");
user.setPhone("13812345678"); // 真实手机号
user.setEmail("zhangsan@example.com");
return user;
}
}
访问接口后,返回的JSON数据如下:
json{
"id": 1,
"username": "张三",
"phone": "138****5678", // 已脱敏
"email": "zhangsan@example.com"
}
| 输入手机号 | 输出结果 | 说明 |
|---|---|---|
| null | null | 空值原样返回 |
| "" | "" | 空字符串原样返回 |
| "13812345678" | "138****5678" | 合法手机号脱敏 |
| "123456" | "123456" | 非手机号格式原样返回 |
| "138123456789" | "138123456789" | 长度不符原样返回 |
如果需要自定义脱敏规则(比如保留前2位、后3位),可以修改序列化器:
java// 自定义脱敏逻辑示例:保留前2位,后3位,中间用*填充
String prefix = phone.substring(0, 2);
String suffix = phone.substring(phone.length() - 3);
String desensitizedPhone = prefix + "*****" + suffix;
如果需要对整个项目的手机号字段统一脱敏,可结合Spring Boot的配置,实现全局序列化规则,无需逐个字段加注解。
可基于该思路扩展更多脱敏注解,如@IdCardDesensitization(身份证脱敏)、@BankCardDesensitization(银行卡脱敏)等,只需替换对应的序列化器即可。
本文通过自定义Jackson注解+序列化器的方式,结合Hutool工具类,实现了优雅、通用的手机号脱敏方案。核心要点如下:
@JsonSerialize注解指定自定义序列化器,实现字段级别的序列化控制该方案不仅适用于手机号脱敏,也可扩展到身份证、银行卡等其他敏感信息的脱敏场景,是后端开发中处理敏感数据的最佳实践之一。
本文作者:JACK WEI
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!