一、前言
什么是数据脱敏呢?我相信小伙伴们多多少少都遇到过或者听到过以下这些需求:
- 手机号中间四位用*显示,比如:
17801234118
→178****0118
- 身份证中间八位用*显示,比如:
450722199901012069
→450722********2069
这里只列举了两个例子,还有很多场景的,比如银行卡、姓名这些,需求要求只展示部分数据,但是不能全部都展示出来,防止敏感信息泄露,这就是数据脱敏。
二、实现数据脱敏的方案
方案一:使用Hutool提供的工具类
这个方式应该是最简便的了,拿来即用。

DesensitizedUtil工具类中还提供有很多场景下的数据脱敏方法,大家可以自行测试。
方案二:利用正则表达式处理
java
public class SensitiveUtil {
public static String maskPhone(String phone) {
if (phone.length() != 11) {
return phone;
}
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
public static String maskIdCard(String idCard) {
if (idCard.length() < 15) {
return idCard;
}
return idCard.replaceAll("(\\d{6})\\d{8}(\\w{4})", "$1********$2");
}
public static String maskEmail(String email) {
if (!email.contains("@")) {
return email;
}
String[] parts = email.split("@");
if (parts[0].length() <= 4) {
return parts[0].substring(0, 1) + "****@" + parts[1];
}
return parts[0].substring(0, 4) + "****@" + parts[1];
}
public static String maskBankCard(String bankCard) {
if (bankCard.length() < 8) {
return bankCard;
}
return bankCard.replaceAll("(\\d{4})\\d{8}(\\d{4})", "$1********$2");
}
}
方案三:自定义注解+AOP实现数据脱敏
java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Desensitize {
// 定义脱敏类型,比如手机号、银行卡号、身份证号等
DesensitizeType type();
}
java
public enum DesensitizeType {
PHONE, // 手机号
ID_CARD, // 身份证号
BANK_CARD // 银行卡号
}
java
@Component
public class DesensitizeProcessor {
// 根据传入的值和脱敏类型,进行数据脱敏
public String desensitize(String value, DesensitizeType type) {
// 根据不同的脱敏类型,执行不同的脱敏逻辑
switch (type) {
case PHONE:
// 把手机号中间四位替换成****
return value.replaceAll("(\d{3})\d{4}(\d{4})", "$1****$2");
case ID_CARD:
// 把身份证号中间八位替换成********
return value.replaceAll("(\d{6})\d{8}(\d{4})", "$1********$2");
case BANK_CARD:
// 把银行卡号中间十二位替换成************
return value.replaceAll("(\d{4})\d{12}(\d{4})", "$1************$2");
default:
return value;
}
}
}
大家可以看到,实际进行数据脱敏处理的逻辑,跟第二个方案中的是一样的,都是运用了正则表达式。
java
@Aspect
@Component
public class DesensitizeAspect {
@Autowired
private DesensitizeProcessor desensitizeProcessor;
/**
* 定义切面的切点,当方法上有@ResponseBody时触发
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("@annotation(org.springframework.web.bind.annotation.ResponseBody)")
public Object desensitize(ProceedingJoinPoint joinPoint) throws Throwable {
// 先执行目标方法,获取返回结果
Object result = joinPoint.proceed();
if (result != null) {
// 对返回结果进行脱敏处理
handleObject(result);
}
return result;
}
private void handleObject(Object obj) {
Class<?> clazz = obj.getClass();
// 获取类的所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 获取字段上的Desensitize注解
Desensitize desensitize = field.getAnnotation(Desensitize.class);
if (desensitize != null) {
// 设置字段可访问
field.setAccessible(true);
try {
// 获取字段值
Object value = field.get(obj);
if (value instanceof String) {
// 如果值是字符串类型,进行脱敏处理
String res = desensitizeProcessor.desensitize((String) value, desensitize.type());
// 把脱敏后的值重新设置回字段
field.set(obj, res);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 如果字段类型是复杂对象,则进行递归处理
if (isComplexObject(field.getType())) {
handleObject(ReflectionUtils.getField(field, obj));
}
}
}
/**
* 用于判断一个类是否为复杂对象。它通过以下条件进行判断:
* 不是基本数据类型(如int、boolean等)
* 不是String类
* 不是枚举类型
* 是一个有效的类(非接口、注解等)
* 满足以上所有条件时,该方法返回true,表示这是一个复杂对象。
* @param clazz
* @return
*/
private boolean isComplexObject(Class<?> clazz) {
return !clazz.isPrimitive() && !clazz.equals(String.class) && !clazz.isEnum() && clazz.isClass();
}
}
在接口返回数据给前端时,该切面类会检查返回对象中是否有带有@Desensitize
注解的字段,有的话则用DesensitizeProcessor进行脱敏处理。
我们只需要在需要脱敏的字段加上@Desensitize
注解即可。