在一些场景下经常需要对一些敏感数据响应给前端的时候进行脱敏操作, 但是直接进行手动脱敏又太笨重, 且不够灵活, 于是想到通过aop实现自动的脱敏功能, 使用时只需要在方法上加注解, 标记切点, 同时对需要脱敏的字段标记脱敏类型即可.
不过, 目前这个代码不支持处理循环引用, 如果出现循环引用使用此注解会无限递归, 导致程序运行出错
首先定义一个作用于方法的注解用于标记哪些方法需要进行数据脱敏
/**
* 用于标记哪些方法需要进行脱敏
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableDataDesensitization {
}
再定义一个注解用于设置脱敏的类型, 以应对不同需求的脱敏, 比如 手机号脱敏 隐藏中间四位, 身份证脱敏 隐藏中间七位
/**
* 标记需要脱敏的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Desensitize {
/**
* 脱敏类型 默认 保留前一位和后一位
*/
DesensitizeTypeEnum type() default DesensitizeTypeEnum.DEFAULT;
/**
* 自定义正则(当type为CUSTOM时生效)
*/
String pattern() default "";
/**
* 替换字符
*/
char replaceChar() default '*';
}
/**
* 脱敏类型枚举
*/
public enum DesensitizeTypeEnum {
/**
* 默认方式(保留前1后1)
*/
DEFAULT,
/**
* 中文名(保留前1位)
*/
CHINESE_NAME,
/**
* 身份证号(保留前3后4)
*/
ID_CARD,
/**
* 手机号(保留前3后4)
*/
PHONE,
/**
* 银行卡(保留前4后4)
*/
BANK_CARD,
/**
* 邮箱(保留@前1位)
*/
EMAIL,
/**
* 自定义
*/
CUSTOM
}
定义切面类
@Aspect
@Component
public class DesensitizeAspect {
@Pointcut("@annotation(com.example.annotations.EnableDataDesensitization)")
public void desensitizePointcut() {}
@AfterReturning(pointcut = "desensitizePointcut ()", returning = "result")
public void afterReturning(Object result) throws IllegalAccessException {
if (result == null) {
return;
}
// 处理单个对象
if (!isContainer(result)) {
desensitizeObject(result);
return;
}
// 处理集合
if (result instanceof Collection) {
for (Object item : (Collection<?>) result) {
desensitizeObject(item);
}
return;
}
// 处理数组
if (result.getClass().isArray()) {
for (Object item : (Object[]) result) {
desensitizeObject(item);
}
}
}
private Object desensitizeObject(Object obj) throws IllegalAccessException {
if (obj == null) {
return null;
}
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 解除访问权限
ReflectionUtils.makeAccessible(field);
// 如果字段上有@Desensitize 注解则进行脱敏处理
if (field.isAnnotationPresent(Desensitize.class)) {
Object fieldValue = field.get(obj);
if (fieldValue instanceof String value) {
Desensitize annotation = field.getAnnotation(Desensitize.class);
field.set(obj, desensitize(value, annotation));
}
}
// 递归处理嵌套对象
Object fieldValue = field.get(obj);
// 如果对象是非Java核心类则进行脱敏
if (fieldValue != null && !isJavaClass(field.getType()) && !isContainer(fieldValue)) {
desensitizeObject(fieldValue);
}
// 如果是集合
if (fieldValue != null && fieldValue instanceof Collection) {
for (Object item : (Collection<?>) fieldValue) {
desensitizeObject(item);
}
}
// 如果是数组
if (fieldValue != null && fieldValue.getClass().isArray()) {
for (Object item : (Object[]) fieldValue) {
desensitizeObject(item);
}
}
}
return obj;
}
private boolean isContainer(Object obj) {
return obj instanceof Collection || obj instanceof Map || obj.getClass().isArray();
}
private boolean isJavaClass(Class<?> clazz) {
return clazz != null && clazz.getClassLoader() == null;
}
/**
* 根据脱敏类型, 对数据进行脱敏处理
*/
public static String desensitize(String value, Desensitize annotation) {
if (value == null || value.isEmpty()) {
return value;
}
DesensitizeTypeEnum type = annotation.type();
char replaceChar = annotation.replaceChar();
return switch (type) {
case CHINESE_NAME -> handleChineseName(value, replaceChar);
case ID_CARD -> handleIdCard(value, replaceChar);
case PHONE -> handlePhone(value, replaceChar);
case BANK_CARD -> handleBankCard(value, replaceChar);
case EMAIL -> handleEmail(value, replaceChar);
case CUSTOM -> handleCustom(value, annotation.pattern(), replaceChar);
default -> handleDefault(value, replaceChar);
};
}
private static String handleChineseName(String fullName, char replaceChar) {
if (fullName.length() <= 1) return fullName;
return fullName.charAt(0) + String.valueOf(replaceChar).repeat(fullName.length() - 1);
}
private static String handleIdCard(String idCard, char replaceChar) {
if (idCard.length() <= 7) return idCard;
return idCard.substring(0, 3) + String.valueOf(replaceChar).repeat(idCard.length() - 7)
+ idCard.substring(idCard.length() - 4);
}
private static String handlePhone(String phone, char replaceChar) {
if (phone.length() <= 7) return phone;
return phone.substring(0, 3) + String.valueOf(replaceChar).repeat(phone.length() - 7)
+ phone.substring(phone.length() - 4);
}
private static String handleBankCard(String bankCard, char replaceChar) {
if (bankCard.length() <= 8) return bankCard;
return bankCard.substring(0, 4) + String.valueOf(replaceChar).repeat(bankCard.length() - 8)
+ bankCard.substring(bankCard.length() - 4);
}
private static String handleEmail(String email, char replaceChar) {
int atIndex = email.indexOf('@');
if (atIndex <= 1) return email;
return email.charAt(0) + String.valueOf(replaceChar).repeat(atIndex - 1)
+ email.substring(atIndex);
}
private static String handleCustom(String value, String pattern, char replaceChar) {
if (pattern == null || pattern.isEmpty()) {
return value;
}
return value.replaceAll(pattern, String.valueOf(replaceChar));
}
private static String handleDefault(String value, char replaceChar) {
if (value.length() <= 1) return value;
int keepPrefix = 1;
int keepSuffix = 1;
if (keepPrefix + keepSuffix >= value.length()) {
return value;
}
return value.substring(0, keepPrefix)
+ String.valueOf(replaceChar).repeat(value.length() - keepPrefix - keepSuffix)
+ value.substring(value.length() - keepSuffix);
}
}