• Springboot 框架中加解密字段后存储数据库


    为防止数据库泄露,表里的敏感字段被曝光,需要对用户的重要数据做加密存取。

    选择加密算法: 首先,你需要选择适合你的需求的加密算法。一些常见的加密算法包括AES、RSA、SHA等。具体的选择取决于你要加密的数据和安全需求。

    引入加密库: 在你的Spring Boot项目中,引入适用于你所选加密算法的库。例如,如果你选择使用AES加密,可以引入Java的javax.crypto库。

    创建加密服务: 创建一个用于加密和解密的服务类。这个服务类应该包含加密和解密方法,并将其注入到Spring容器中以供使用。

    定义数据库实体类: 在数据库实体类中,将需要加密的字段定义为加密前的原始字段。在将数据保存到数据库之前,使用加密服务对这些字段进行加密。

    加密和解密数据: 在业务逻辑中,调用加密服务对需要加密的数据进行加密,并在需要时进行解密。确保加密密钥的安全存储,不要将密钥硬编码在代码中。

    定义接口文件--------------------------------------------------

    DecryptField.java :

    package xxx.xxx

    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.ElementType;

    //解密字段注解
    @Target(value = {ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DecryptField {
    }

    EncryptField .java :

    //解密字段注解
    @Target(value = {ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface EncryptField {
    }

    NeedDecrypt .java :

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

    NeedEncrypt .java :

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

    AesSupport.java 加密解密算法------------------------------------------

    public class AesSupport{
        
        private static final String KEY_ALGORITHM = "AES";
        private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
        private static final String AES_KEY = "you aes key"; // 16/24/32-character secret key

        private String password;
        private SecretKeySpec secretKeySpec;
        private static AesSupport instance;

        // 私有构造函数,防止外部创建实例
        private AesSupport() {
        }

        // 提供获取单例实例的方法,使用双重检查锁定保证线程安全
        public static AesSupport getInstance() throws NoSuchAlgorithmException {
            if (instance == null) {
                synchronized (AesSupport.class) {
                    if (instance == null) {
                        instance = new AesSupport(AES_KEY);
                    }
                }
            }
            return instance;
        }

        public AesSupport(String password) throws NoSuchAlgorithmException {

            if(StringUtils.isEmpty(password)){
                throw new IllegalArgumentException("password should not be null!");
            }

            this.password = password;
            this.secretKeySpec = getSecretKey(password);
        }

        public String encrypt(String value){

            try{
                Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

                byte[] content = value.getBytes("UTF-8");
                byte[] encryptData = cipher.doFinal(content);

                return Hex.bytesToHexString(encryptData);
            }catch (Exception e){
                throw new IllegalStateException("AES encrypt "+e.getMessage(),e);
            }
        }

        public String decrypt(String value){
            if (StringUtils.isEmpty(value)) {
                return "";
            }
            try {
                byte[] encryptData = Hex.hexStringToBytes(value);
                Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
                byte[] content = cipher.doFinal(encryptData);
                return new String(content, "UTF-8");
            }catch (Exception e){
                throw new IllegalStateException("AES decrypt "+e.getMessage(),e);
            }

        }


        private SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException{
            KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            random.setSeed(password.getBytes());
            kg.init(128, random);
            SecretKey secretKey = kg.generateKey();
            return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
        }
    }
     

    DecryptAop.java 解密Aop文件:

    @Component
    @Aspect
    public class DecryptAop {
       
        /**
         * 定义需要解密的切入点
         */
        @Pointcut(value = "@annotation(xxx..service.crypt.NeedDecrypt)")
        public void pointcut() {
        }
     
        /**
         * 命中的切入点时的环绕通知
         *
         * @param proceedingJoinPoint
         * @return
         * @throws Throwable
         */
        @Around("pointcut()")
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            //执行目标方法
            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();
                            //加密数据的解密处理
                            try {
                                value = AesSupport.getInstance().decrypt(value);
                            } catch (Exception e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                            DecryptField annotation = declaredField.getAnnotation(DecryptField.class);
                            //boolean open = annotation.open();
                            //把解密后的数据重新赋值
                            declaredField.set(obj, value);
                        }
                    }
                }
            }
        }

    }


    EncryptAop.java 加密Aop文件:

    @Component
    @Aspect
    public class EncryptAop {

        /**
         * 定义加密切入点
         */
        @Pointcut(value = "@annotation(xxx.service.crypt.NeedEncrypt)")
        public void pointcut() {
        }
     
        /**
         * 命中加密切入点的环绕通知
         *
         * @param proceedingJoinPoint
         * @return
         * @throws Throwable
         */
        @Around("pointcut()")
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

            //获取命中目标方法的入参数
            Object[] args = proceedingJoinPoint.getArgs();
            if (args.length > 0) {
                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();
            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;
                            try {
                                decrypt = AesSupport.getInstance().encrypt(value);
                                 //把加密后的字段属性值重新赋值
                                declaredField.set(obj, decrypt);
                            } catch (Exception e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }

    }

    通过Aop切入拦截标注了NeedEncrypt 、NeedDecrypt的接口函数,获取到参数字段判断是否标注了DecryptField、EncryptField,对标注的进行加解密操作,最后存储到数据库。

    在Bean对象类进行字段标注:

    public class User{
        private String id;
        @EncryptField
        @DecryptField
        private String name;
        @EncryptField
        @DecryptField
        private String phonenum;

    }

    在接口方法上进行方法标注:

     @NeedEncrypt
      public int updateUser(User user) throws Exception {

      }

    @NeedDecrypt
      public List queryUser(String userId) throws Exception {

      }

    注意不要在接口调用路径上进行重复标注,否则会加密两次,最后导致解密不出来。

  • 相关阅读:
    Kimi多线程批量写原创文章API软件
    Session
    MySQL 多表关联一对多查询实现取最新一条数据
    ERC-3525 通过倒计时 SFT 是什么?有什么用?
    正缘画像 api数据接口
    Docker 【数据卷与Dockerfile构建镜像】
    FITC标记的大鼠抗小鼠CD11b抗体,FITC Rat Anti-Mouse CD11b
    【仿牛客网笔记】初识Spring Boot,开发社区首页-MyBatis入门
    「Paraverse平行云」受邀参与编写国内首个3D数字内容生产技术白皮书
    UG NX二次开发(C#)- 制图(Draft)-工程图框选制图曲线并输出制图曲线的信息
  • 原文地址:https://blog.csdn.net/jeson1224/article/details/133684996