• Springboot AOP实现指定敏感字段数据加密


    1. 插入数据 自定义注解方式  对 指定接口方法 的 参数的指定字段进行 加密存储;

    2.对数据内的加密数据,进行解密返回

    先看看效果 : 

    数据存入数据库表内, 手机号phone和邮箱email 属于敏感数据,我们需要密文存储 : 

    查询解密返回:

    1.  自定义注解 加密标识注解  NeedEncrypt.java :

    import java.lang.annotation.*;
     
    /**
     * @Author JCccc
     * @Description 需加密
     * @Date 2021/7/23 11:55
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NeedEncrypt {
     
     
    }
    2.自定义注解 需加密字段标识注解 EncryptField.java :

    /**
     * @Author JCccc
     * @Description
     * @Date 2021/7/23 11:55
     */
    @Target({ElementType.FIELD,ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface EncryptField {
     
        String[] value() default "";
    }
    3.加密逻辑的aop处理器  EncryptAspect.java :

    import com.elegant.dotest.aop.annotation.EncryptField;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.jasypt.encryption.StringEncryptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
     
    import java.lang.reflect.Field;
    import java.util.Objects;
     
    /**
     * @Author JCccc
     * @Description
     * @Date 2021/9/14 8:55
     */
    @Slf4j
    @Aspect
    @Component
    public class EncryptAspect {
     
        @Autowired
        private StringEncryptor stringEncryptor;
     
        @Pointcut("@annotation(com.elegant.dotest.aop.annotation.NeedEncrypt)")
        public void pointCut() {
        }
     
        @Around("pointCut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            //加密
            encrypt(joinPoint);
     
            return joinPoint.proceed();
        }
     
        public void encrypt(ProceedingJoinPoint joinPoint)  {
            Object[] objects=null;
            try {
                 objects = joinPoint.getArgs();
                if (objects.length != 0) {
                    for (int i = 0; i < objects.length; i++) {
                        //抛砖引玉 ,可自行扩展其他类型字段的判断
                        if (objects[i] instanceof String) {
                            objects[i] = encryptValue(objects[i]);
                        } else {
                            encryptObject(objects[i]);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
     
        /**
         * 加密对象
         * @param obj
         * @throws IllegalAccessException
         */
        private void encryptObject(Object obj) throws IllegalAccessException {
     
            if (Objects.isNull(obj)) {
                log.info("当前需要加密的object为null");
                return;
            }
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields) {
                boolean containEncryptField = field.isAnnotationPresent(EncryptField.class);
                if (containEncryptField) {
                    //获取访问权
                    field.setAccessible(true);
                    String value = stringEncryptor.encrypt(String.valueOf(field.get(obj)));
                    field.set(obj, value);
                }
            }
        }
     
        /**
         * 加密单个值
         * @param realValue
         * @return
         */
        public String encryptValue(Object realValue) {
            try {
                realValue = stringEncryptor.encrypt(String.valueOf(realValue));
            } catch (Exception e) {
                log.info("加密异常={}",e.getMessage());
            }
            return String.valueOf(realValue);
        }
     
     
    }
    4. 插入user表 使用的 User.java :
     

    import com.elegant.dotest.aop.annotation.EncryptField;
    import lombok.Data;
    import lombok.experimental.Accessors;
     
    /**
     * @Author JCccc
     * @Description
     * @Date 2021/9/14 8:55
     */
    @Data
    @Accessors(chain = true)
    public class User {
     
        private Integer id;
        private String name;
        @EncryptField
        private String phone;
        @EncryptField
        private String email;
        private Integer age;
     
    }
    可以看到,手机号phone 和 邮箱 email 两个字段,我们做了注解 @EncryptField 标识:

    ok,我们写个测试接口,使用 @NeedEncrypt 注解标识这个接口需要进行加密拦截 :

    使用postman调用一下测试接口:


     可以看下数据库,数据已经加密存储成功:

    接下来是查询解密环节:

    解密这里其实有些小讲究。 因为查询出来的数据有可能是单个实体,也可能是List (其实甚至是Map或者Set,又或者是 分页数据类)

    所以本文将会以 最常用的 单个实体 、 List<实体> 为例子,去做解密。

    1.解密自定义注解 NeedDecrypt.java :
     

    /**
     * @Author JCccc
     * @Description 需解密
     * @Date 2021/7/23 11:55
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NeedDecrypt {
     
    }
    2. 解密逻辑的aop处理器  DecryptAspect.java :
     

    import com.elegant.dotest.aop.annotation.EncryptField;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.jasypt.encryption.StringEncryptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
     
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Objects;
     
    /**
     * @Author JCccc
     * @Description
     * @Date 2021/9/14 8:55
     */
    @Slf4j
    @Aspect
    @Component
    public class DecryptAspect {
     
        @Autowired
        private StringEncryptor stringEncryptor;
     
        @Pointcut("@annotation(com.elegant.dotest.aop.annotation.NeedDecrypt)")
        public void pointCut() {
        }
     
        @Around("pointCut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            //解密
            Object result = decrypt(joinPoint);
            return result;
        }
     
        public Object decrypt(ProceedingJoinPoint joinPoint) {
            Object result = null;
            try {
                Object obj = joinPoint.proceed();
                if (obj != null) {
                    //抛砖引玉 ,可自行扩展其他类型字段的判断
                    if (obj instanceof String) {
                        decryptValue(obj);
                    } else {
                        result = decryptData(obj);
                    }
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }
            return result;
        }
     
        private Object decryptData(Object obj) throws IllegalAccessException {
     
            if (Objects.isNull(obj)) {
                return null;
            }
            if (obj instanceof ArrayList) {
                decryptList(obj);
            } else {
                decryptObj(obj);
            }
     
     
            return obj;
        }
     
        /**
         * 针对单个实体类进行 解密
         * @param obj
         * @throws IllegalAccessException
         */
        private void decryptObj(Object obj) throws IllegalAccessException {
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields) {
                boolean hasSecureField = field.isAnnotationPresent(EncryptField.class);
                if (hasSecureField) {
                    field.setAccessible(true);
                    String realValue = (String) field.get(obj);
                    String value = stringEncryptor.decrypt(realValue);
                    field.set(obj, value);
                }
            }
        }
     
        /**
         * 针对list<实体来> 进行反射、解密
         * @param obj
         * @throws IllegalAccessException
         */
        private void decryptList(Object obj) throws IllegalAccessException {
            List<Object> result = new ArrayList<>();
            if (obj instanceof ArrayList) {
                for (Object o : (List<?>) obj) {
                    result.add(o);
                }
            }
            for (Object object : result) {
                decryptObj(object);
            }
        }
     
     
        public String decryptValue(Object realValue) {
            try {
                realValue = stringEncryptor.encrypt(String.valueOf(realValue));
            } catch (Exception e) {
                log.info("解密异常={}", e.getMessage());
            }
            return String.valueOf(realValue);
        }
     
     
    }
    然后我们对一个查询方法进行测试 :


     我们先试一下查询单条数据的:

    使用@NeedDecrypt注解标记这个接口需要数据解密:

    调用接口看看结果:

     然后是多条数据List<User> 返回的接口:


     调用接口测试看看结果:

  • 相关阅读:
    软考-系统开发基础
    【visual studio】visual studio 2022 无法 复制黏贴
    Java 程序员从携程、美团、阿里面试回来,这些面经分享给大家
    第二节——Vue 基本介绍
    (七)CSharp-刘铁锰版-事件
    Linux文件的atime, mtime, ctime属性以及修改
    pymavlink简单使用
    华为任正非:活下来!
    对Flutter GetX的一些理解
    脚手架构建VUE项目
  • 原文地址:https://blog.csdn.net/educast/article/details/125479021