数据脱敏这一块已经有好多的框架供我们选择,但是在一些特殊的业务创景还是不能满足,比如根据用户权限,岗位,部门等 进行数据脱敏,需求真的可以说是五花八门,这时候借助第三方库处理就比较头疼了。
实现基本思路 自定义注解 + AOP + 反射 。
自定义注解类
package com.jinhao.demo.annotation;
import com.jinhao.demo.enums.DesensitizationType;
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 Desensitization {
DesensitizationType value() default DesensitizationType.DEfAULT;
}
脱敏类型枚举类
package com.jinhao.demo.enums;
/**
* @author jinhao
*/
public enum DesensitizationType {
/**
* 默认脱敏
*/
DEfAULT,
/**
* 手机号脱敏
*/
PHONE,
/**
* 邮箱脱敏
*/
EMAIL,
}
切面类
package com.jinhao.demo.Aspect;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jinhao.demo.annotation.Desensitization;
import com.jinhao.demo.enums.DesensitizationType;
import com.mysql.cj.util.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;
/**
* 数据脱敏切面
* @author jinhao
*/
@Aspect
@Component
public class DesensitizationAspect {
@Around("execution(* com.jinhao.demo.controller.*.*(..))")
private Object aspect(ProceedingJoinPoint joinPoint) throws Throwable{
Object result = joinPoint.proceed();
if(result instanceof Page<?> page){
Object data = page.getRecords();
if (data instanceof List<?> list){
list.forEach(s->{
// 得到了 实体类 实例 s
Class<?> entity = s.getClass();
Field[] fields = entity.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//得到字段注解集合
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(Desensitization.class)) {
if (field.getType() == String.class) {
try {
String value = (String) field.get(s);
// 获取指定脱敏策略 也就是注解的值 是DesensitizationType 类型
DesensitizationType desensitizationType = field.getAnnotation(Desensitization.class).value();
field.set(s,desensitizationStrategy(desensitizationType, value));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
});
}
}
return result;
}
public String desensitizationStrategy(DesensitizationType desensitizationType,String value) {
switch (desensitizationType) {
case DEfAULT -> value = "xxxxxx";
case PHONE -> value = maskPhoneNumber(value);
case EMAIL -> value = maskEmail(value);
default -> {
}
}
return value;
}
public static String maskPhoneNumber(String phoneNumber) {
int phoneNumberLength = 11;
// 检查电话号码长度是否为11位
if (phoneNumber == null || phoneNumber.length() != phoneNumberLength) {
return phoneNumber;
}
// 提取前三位和最后四位,中间用"xxxx"替换
return phoneNumber.substring(0, 3) + "xxxx" + phoneNumber.substring(7);
}
public static String maskEmail(String phoneNumber) {
// 检查电话号码长度是否为11位
// 提取前三位和最后四位,中间用"xxxx"替换
if (StringUtils.isNullOrEmpty(phoneNumber)) {
return phoneNumber;
}
try {
phoneNumber = phoneNumber.split("@")[1];
} catch (Exception x) {
return phoneNumber;
}
return "xxxxxxxxxx@" + phoneNumber;
}
}
代码分析
1 首先我们需要有一个切面类 DesensitizationAspect
/**
* 数据脱敏切面
* @author jinhao
*/
@Aspect
@Component
public class DesensitizationAspect {
@Around("execution(* com.jinhao.demo.controller.*.*(..))")
private Object aspect(ProceedingJoinPoint joinPoint) throws Throwable{
}
}
2 指定切面过滤规则为我们的Controller包下的所有方法,这个更具需要自己调整。
@Around("execution(* com.jinhao.demo.controller.*.*(..))")
3 拿到切入点 方法的返回值
Object result = joinPoint.proceed();
4 对返回类型进行过滤 这个我只对 Page<?> page 分类数据进行处理 确定了返回值类型 我们就可以 拿到里面实体类的实例了 大家根据自己的项目调整即可
5 因为我们的注解是写在实体类的字段上的,就需要检查这个实体类字段是否有我们的数据脱敏注解
for (Field field : fields) {
field.setAccessible(true);
//得到字段注解集合
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(Desensitization.class)) {
//确定这个实体类有@Desensitization 注解
}
}
}
5 针对不同的字段类型判断处理 这里显示时我只对String 类型字段进行处理
if (field.getType() == String.class) {
try {
String value = (String) field.get(s);
// 获取指定脱敏策略 也就是注解的值 是DesensitizationType 类型
DesensitizationType desensitizationType = field.getAnnotation(Desensitization.class).value();
field.set(s,desensitizationStrategy(desensitizationType, value));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
6给需要脱敏的字段添加 @Desensitization 注解
@Desensitization
private String studentName;
进行测试
发现 studentName 字段已经脱敏成功 。 这里我为了方便 只定义了两个简单的脱敏方法,建议大家使用策略模式实现方便后续扩展不同的脱敏逻辑。