本文目录导读:

在Java中实现数据脱敏,通常有几种常见的方法:字符串替换、注解+AOP、MyBatis插件、以及JSON序列化拦截,以下是几种主流且实用的实现方案及其代码示例。
工具类方法(最基础,手动调用)
适用于简单场景,如日志打印、手动处理单个字段。
public class DesensitizedUtil {
/**
* 手机号脱敏:保留前3后4
*/
public static String mobile(String phone) {
if (phone == null || phone.length() != 11) return phone;
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
/**
* 身份证号脱敏:保留前6后4
*/
public static String idCard(String id) {
if (id == null || id.length() < 10) return id;
return id.replaceAll("(\\d{6})\\d{8,12}(\\d{4})", "$1********$2");
}
/**
* 邮箱脱敏:用户名部分显示首字母+***
*/
public static String email(String email) {
if (email == null || !email.contains("@")) return email;
String name = email.substring(0, email.indexOf("@"));
String domain = email.substring(email.indexOf("@"));
return name.charAt(0) + "***" + domain;
}
/**
* 姓名脱敏:显示姓氏,名字用*代替(中文)
*/
public static String name(String name) {
if (name == null || name.length() <= 1) return name;
if (name.length() == 2) {
return name.charAt(0) + "*";
}
return name.charAt(0) + "*" + name.substring(name.length() - 1);
}
}
使用示例:
System.out.println(DesensitizedUtil.mobile("13812345678")); // 138****5678
基于注解 + AOP(推荐,侵入性低)
适合全局脱敏,尤其是返回给前端的JSON数据。
步骤:
- 定义脱敏枚举和注解
- 使用 Jackson 自定义序列化器
- 在字段上加注解
1 定义脱敏类型枚举
public enum DesensitizedType {
MOBILE, // 手机号
ID_CARD, // 身份证
EMAIL, // 邮箱
NAME // 姓名
}
2 自定义注解
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizedSerializer.class)
public @interface Sensitive {
DesensitizedType value();
}
3 自定义 Jackson 序列化器
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class DesensitizedSerializer extends JsonSerializer<String> {
private DesensitizedType type;
public DesensitizedSerializer(DesensitizedType type) {
this.type = type;
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
switch (type) {
case MOBILE:
gen.writeString(DesensitizedUtil.mobile(value));
break;
case ID_CARD:
gen.writeString(DesensitizedUtil.idCard(value));
break;
case EMAIL:
gen.writeString(DesensitizedUtil.email(value));
break;
case NAME:
gen.writeString(DesensitizedUtil.name(value));
break;
default:
gen.writeString(value);
}
}
}
4 在实体类中使用
public class UserDTO {
@Sensitive(DesensitizedType.NAME)
private String name;
@Sensitive(DesensitizedType.MOBILE)
private String phone;
@Sensitive(DesensitizedType.EMAIL)
private String email;
// getter/setter
}
这样,当对象通过 Jackson 序列化返回给前端时,会自动脱敏。
MyBatis 插件方式(数据库层面拦截)
适用于从数据库查询出来后自动脱敏,对业务代码完全无侵入。
1 自定义注解(用在实体字段上)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {
DesensitizedType value();
}
2 MyBatis 拦截器(ResultSetHandler)
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.*;
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DesensitizationInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
List<Object> results = (List<Object>) invocation.proceed();
for (Object result : results) {
if (result != null) {
process(result);
}
}
return results;
}
private void process(Object obj) throws IllegalAccessException {
Class<?> clazz = obj.getClass();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(SensitiveField.class)) {
SensitiveField annotation = field.getAnnotation(SensitiveField.class);
field.setAccessible(true);
String value = (String) field.get(obj);
if (value != null) {
String desensitized = doDesensitize(value, annotation.value());
field.set(obj, desensitized);
}
}
}
}
private String doDesensitize(String value, DesensitizedType type) {
switch (type) {
case MOBILE: return DesensitizedUtil.mobile(value);
case ID_CARD: return DesensitizedUtil.idCard(value);
case EMAIL: return DesensitizedUtil.email(value);
case NAME: return DesensitizedUtil.name(value);
default: return value;
}
}
}
注册插件(Spring Boot + MyBatis配置):
@Configuration
public class MyBatisConfig {
@Bean
public Interceptor desensitizationInterceptor() {
return new DesensitizationInterceptor();
}
}
实体类使用:
public class User {
@SensitiveField(DesensitizedType.NAME)
private String name;
@SensitiveField(DesensitizedType.MOBILE)
private String phone;
}
日志脱敏(配合 Logback / Log4j)
使用 MessageConverter 或自定义 Layout,对日志中的敏感信息进行替换。
示例(基于正则替换,Logback)
public class SensitiveDataConverter extends MessageConverter {
@Override
public String convert(ILoggingEvent event) {
String msg = event.getFormattedMessage();
// 手机号:3-4-4格式
msg = msg.replaceAll("(1[3-9]\\d)\\d{4}(\\d{4})", "$1****$2");
// 身份证:18位
msg = msg.replaceAll("(\\d{6})\\d{8,12}(\\d{4})", "$1********$2");
return msg;
}
}
logback.xml配置:
<conversionRule conversionWord="msg" converterClass="com.example.SensitiveDataConverter"/>
如何选择?
| 场景 | 推荐方案 |
|---|---|
| 简单的手动处理(打印/日志) | 工具类 |
| 后端返回给前端的JSON自动脱敏 | 注解+Jackson序列化 |
| 数据库查询后全局脱敏(无侵入) | MyBatis插件 |
| 日志脱敏 | Logback/Layout转换器 |
| 非对称脱敏(保留可逆) | AES加密 + 脱敏规则组合(更安全) |
如果你的项目是 Spring Boot + Jackson(绝大多数),方案2(注解+Jackson序列化) 是最优雅、最常用的方式。