数据库字段加解密

1. JoinPoint

1.1 介绍

Joinpoint是面向切面编程(AOP)中的一个重要概念,指的是在应用程序执行过程中可以被拦截的特定点。在AOP中,Joinpoint代表了程序执行的某个具体位置,比如方法的调用、异常的抛出等。AOP框架通过拦截这些Joinpoint来插入额外的逻辑,实现横切关注点的功能。
我们可以通过JoinPoint获取到除了异常参数对象和返回值之外的所有信息
返回值截图:
在这里插入图片描述

1.2 获取

获取方法调用时,传入的参数

Object[] args = joinPoint.getArgs();

获取被通知的目标对象

Object target = joinPoint.getTarget();

获取代理方法的信息

MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 获取代理方法的信息
String methodName = signature.getName(); // 获取方法名
Class<?> returnType = signature.getReturnType(); // 获取返回类型
Class<?>[] parameterTypes = signature.getParameterTypes(); // 获取参数类型数组

获取增强方法中返回值

@AfterReturning(value = "point()",returning = "ret")
public void methodAfterReturning(JoinPoint joinPoint, Object ret){
    System.out.println("AfterReturning");
}

获取增强方法中的异常对象

@AfterThrowing(value = "point()",throwing = "e")
public void methodAfterThrowing(JoinPoint joinPoint,Throwable e){
    System.out.println("AfterThrowing");
}

在环绕通知中,调用proceed()方法会继续执行被通知的方法。

@Around("execution(* com.example.service.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
    // 执行前置逻辑
    Object result = joinPoint.proceed(); // 继续执行被通知的方法
    // 执行后置逻辑
    return result;
}

2. 数据库加解密

2.1 依赖

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.8.18</version>
		</dependency>

2.2 定义需要加解密的注解

2.2.1 解密字段注解

//解密字段注解
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
    //是否开启敏感数据脱敏处理,默认不开启
    boolean open() default false;
    //脱敏开始位置索引
    int start() default 0;
    //脱敏从开始位置向后偏移量
    int offset() default 6;
}

2.2.2 加密字段注解

@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}

2.2.3 解密处理的方法注解

//作用于对返回值进行解密处理的方法上
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedDecrypt {
}

2.2.4 加密处理的方法注解

// 作用于需要对入参数进行加密处理的方法上
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {
}

2.3 加解密切入点

2.3.1 解密切入点

@Aspect
@Component
@Slf4j
public class DecryptAop {

    /**
     * 定义加密切入点
     */
    @Pointcut(value = "@annotation(com.example.demo.annotation.NeedDecrypt)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("//环绕通知 start");
        //执行目标方法
        Signature signature = proceedingJoinPoint.getSignature();
        Object result = proceedingJoinPoint.proceed();
        //判断目标方法的返回值类型
        if (result instanceof List) {
            for (Object tmp : ((List) result)) {
                //数据脱敏处理逻辑
                this.deepProcess(tmp);
            }
        } else {
            this.deepProcess(result);
        }
        log.info("//环绕通知 end");
        return result;
    }

    public void deepProcess(Object obj) throws IllegalAccessException {
        if (obj != null) {
            //取出输出对象的所有字段属性,并遍历
            Field[] declaredFields = obj.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //判断字段属性上是否标记DecryptField注解
                if (declaredField.isAnnotationPresent(DecryptField.class)) {
                    //如果判断结果为真,则取出字段属性数据进行解密处理
                    declaredField.setAccessible(true);
                    Object valObj = declaredField.get(obj);
                    if (valObj != null) {
                        String value = valObj.toString();
                        //加密数据的解密处理
                        value = this.decrypt(value);
                        DecryptField annotation = declaredField.getAnnotation(DecryptField.class);
                        boolean open = annotation.open();
                        if (open) {
                            //如果开启,则开始进行数据脱敏处理
                            int start = annotation.start();
                            int offset = annotation.offset();
                            value = this.secret(value, start, offset);
                        }
                        //把解密后的数据重新赋值
                        declaredField.set(obj, value);
                    }
                }
            }
        }
    }

    private String decrypt(String value) {
        //这里特别注意一下,对称加密是根据密钥进行加密和解密的,加密和解密的密钥是相同的,一旦泄漏,就无秘密可言,
        //“fanfu-csdn”就是我自定义的密钥,这里仅作演示使用,实际业务中,这个密钥要以安全的方式存储;
        byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), "shenduxuexizhennan".getBytes()).getEncoded();
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
        String decryptStr = aes.decryptStr(value);
        return decryptStr;
    }

    private String secret(String value, Integer start, Integer limit) {
        //如果有特殊需要,还可以定义其他用于代替敏感数据的字符,一般情况下,使用的是“*”
        char[] chars = value.toCharArray();
        for (int i = start; i < start + limit; i++) {
            chars[i] = '*';
        }
        return String.valueOf(chars);
    }
}

2.3.2 加密切入点

@Aspect
@Component
@Slf4j
public class EncryptAop {

    /**
     * 定义加密切入点
     */
    @Pointcut(value = "@annotation(com.example.demo.annotation.NeedDecrypt)")
    public void pointcut() {
    }
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("//环绕通知 start");
        //获取命中目标方法的入参数
        Object[] args = proceedingJoinPoint.getArgs();
        for (Object arg : args) {
            //按参数的类型进行判断,如果业务中还有其他的类型,可酌情增加
            if (arg != null) {
                if (arg instanceof List) {
                    for (Object tmp : ((List) arg)) {
                        //加密处理
                        this.deepProcess(tmp);
                    }
                } else {
                    this.deepProcess(arg);
                }
            }
        }
        //对敏感数据加密后执行目标方法
        Object result = proceedingJoinPoint.proceed();
        log.info("//环绕通知 end");
        return result;
    }

    public void deepProcess(Object obj) throws IllegalAccessException {
        if (obj != null) {
            //获取对象的所有字段属性并遍历
            Field[] declaredFields = obj.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //判断字段属性上是否标记了@EncryptField注解
                if (declaredField.isAnnotationPresent(EncryptField.class)) {
                    //如果判断结果为真,则取出字段属性值,进行加密、重新赋值
                    declaredField.setAccessible(true);
                    Object valObj = declaredField.get(obj);
                    if (valObj != null) {
                        String value = valObj.toString();
                        //开始敏感字段属性值加密
                        String decrypt = this.encrypt(value);
                        //把加密后的字段属性值重新赋值
                        declaredField.set(obj, decrypt);
                    }
                }
            }
        }
    }

    private String encrypt(String value) {
        //这里特别注意一下,对称加密是根据密钥进行加密和解密的,加密和解密的密钥是相同的,一旦泄漏,就无秘密可言,
        //“fanfu-csdn”就是我自定义的密钥,这里仅作演示使用,实际业务中,这个密钥要以安全的方式存储;
        byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), "shenduxuexizhennan".getBytes()).getEncoded();
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
        String encryptValue = aes.encryptBase64(value);
        return encryptValue;
    }
}

2.4 使用

  1. 把自定义的加密字段注解、解密字段注解标记在需要加密或者解密的字段上;
@Slf4j
@Data
public class Person  {
 private Integer id;
 private String userName;
 private String loginNo;
 @EncryptField
 @DecryptField
 private String phoneNumber;
 private String sex;
 @DecryptField
 @EncryptField
 private String IDCard;
 private String address;
 @EncryptField
 @DecryptField
 private String houseNumber;
}
  1. 把@NeedEncrypt和@NeedDecrypt标记在需要对入参数、返回值中的敏感字段进行加密、解密处理的业务处理方法上;
@RestController
@RequestMapping("/person")
@Slf4j
public class PersonController {
    @Autowired
    private IPersonService personService;
    //添加人员信息
    @PostMapping("/add")
    @NeedEncrypt
    public Person add(@RequestBody Person person, Model model) {
        Person result = this.personService.registe(person);
        log.info("//增加person执行完成");
        return result;
    }
    //人员信息列表查询
    @GetMapping("/list")
    @NeedDecrypt
    public List<Person> getPerson() {
        List<Person> persons = this.personService.getPersonList();
        log.info("//查询person列表执行完成");
        return persons;
    }
    //人员信息详情查询
    @GetMapping("/{id}")
    @NeedDecrypt
    public Person get(@PathVariable Integer id) {
        Person person= this.personService.get(id);
        log.info("//查询person详情执行完成");
        return person;
    }
}
  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值