springboot 自定义注解实现数据脱敏

       数据脱敏这一块已经有好多的框架供我们选择,但是在一些特殊的业务创景还是不能满足,比如根据用户权限,岗位,部门等 进行数据脱敏,需求真的可以说是五花八门,这时候借助第三方库处理就比较头疼了。

     实现基本思路  自定义注解 + 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 字段已经脱敏成功 。 这里我为了方便 只定义了两个简单的脱敏方法,建议大家使用策略模式实现方便后续扩展不同的脱敏逻辑。

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值