【Spring Cloud 指定敏感字段存储数据库加密】

一、前言

在做项目的时候,有的时候会遇到存储银行卡号、邮箱信息入数据库加密的要求,本文使用jasypt进行加密。参考资料:

Springboot AOP实现指定敏感字段数据加密 (数据加密篇 二)

二、AOP实现加密

maven引用


    com.github.ulisesbocchio
    jasypt-spring-boot-starter
    2.1.0


    org.projectlombok
    lombok

【Spring Cloud 指定敏感字段存储数据库加密】_第1张图片

 1、加密

1.1、需要加密的方法自定义注解

package com.sed.commons.sensitive.encrypt;

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

/**
 * 需加密
 * @author sed
 * @date 2021-11-30 09:42:49
 * @Description
 *  注解在接口上,标识这个接口需要进行加密拦截
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {
}

1.2、需要加密的字段自定义注解

package com.sed.commons.sensitive.encrypt;

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

/**
 * 需加密字段标识注解
 * @author sed
 * @date 2021-11-30 09:44:53
 */
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {

    String[] value() default "";
}

1.3、加密逻辑的aop处理器

package com.sed.commons.sensitive.encrypt;

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.jasypt.util.text.BasicTextEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Objects;

/**
 * 加密逻辑的aop处理器
 * @author sed
 * @date 2021-11-30 09:46:13
 */
@Slf4j
@Aspect
@Component
public class EncryptAspect {

    @Autowired
    private StringEncryptor stringEncryptor;

    @Pointcut("@annotation(com.sed.commons.sensitive.encrypt.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 = encrypt(String.valueOf(field.get(obj)));
//                String value = stringEncryptor.encrypt(String.valueOf(field.get(obj)));
                field.set(obj,value);
            }
        }
    }

    public String encryptValue(Object realValue){
        try {
            realValue = encrypt(String.valueOf(realValue));
        }catch (Exception e){
            log.info("加密异常={}",e.getMessage());
        }
        return String.valueOf(realValue);
    }

    private String encrypt(String text){
        BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
        //加密所需的salt(盐)
        textEncryptor.setPassword("EbfYkitulv73I2p0mXI50JMXoaxZTKJ7");
        return textEncryptor.encrypt(text);
    }
}

1.4、加密注解生效启动方式

package com.sed.commons.sensitive.encrypt;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * 加密注解生效启动方式
 * @author sed
 * @date 2021-11-30 11:35:59
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EncryptRegister.class})
public @interface EnableEncrypt {
}

上述@Import,括号中的类是ImportBeanDefinitionRegistrar的实现类,负责调用接口方法,将其中要注册的类注册成bean

1.5、生效配置类

package com.sed.commons.sensitive.encrypt;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;

public class EncryptRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private ResourceLoader resourceLoader;

    public EncryptRegister(){

    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        this.registerEncryptClients(metadata, registry);
    }

    public void registerEncryptClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
        scanner.setResourceLoader(this.resourceLoader);
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(org.springframework.stereotype.Component.class);
        scanner.addIncludeFilter(annotationTypeFilter);
        String basePackage = (ClassUtils.getPackageName(EncryptField.class));
        scanner.scan(basePackage);
    }
}

2、解密

2.1、需要解密的方法自定义注解

package com.sed.commons.sensitive.decrypt;

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

/**
 * 解密

写在实现类

* @author sed * @date 2021-11-30 16:28:28 * @Description * 注解在接口上,标识这个接口需要进行解密拦截 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NeedDecrypt { }

2.2、解密逻辑的aop处理器

package com.sed.commons.sensitive.decrypt;

import com.sed.commons.sensitive.encrypt.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.util.text.BasicTextEncryptor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 解密逻辑的aop处理器
 * @author sed
 * @date 2021-11-30 10:14:53
 */
@Slf4j
@Aspect
@Component
public class DecryptAspect {

    @Pointcut("@annotation(com.sed.commons.sensitive.decrypt.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 = decrypt(realValue);
                field.set(obj, value);
            }
        }
    }

    /**
     * 针对list<实体来> 进行反射、解密
     * @param obj
     * @throws IllegalAccessException
     */
    private void decryptList(Object obj) throws IllegalAccessException {
        List 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 = decrypt(String.valueOf(realValue));
        } catch (Exception e) {
            log.info("解密异常={}", e.getMessage());
        }
        return String.valueOf(realValue);
    }

    private String decrypt(String text){
        BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
        //加密所需的salt(盐)
        textEncryptor.setPassword("EbfYkitulv73I2p0mXI50JMXoaxZTKJ7");
        return textEncryptor.decrypt(text);
    }
}
 
  

2.3、解密注解生效启动方式

package com.sed.commons.sensitive.decrypt;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * 解密注解生效启动方式
 * @author sed
 * @date 2021-11-30 11:35:59
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({DecryptRegister.class})
public @interface EnableDecrypt {
}

2.4、生效配置类

package com.sed.commons.sensitive.decrypt;

import com.sed.commons.sensitive.encrypt.EncryptField;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;

import java.util.List;

public class DecryptRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private ResourceLoader resourceLoader;

    public DecryptRegister(){

    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        this.registerDecryptClients(metadata, registry);
    }

    public void registerDecryptClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
        scanner.setResourceLoader(this.resourceLoader);
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(org.springframework.stereotype.Component.class);
        scanner.addIncludeFilter(annotationTypeFilter);
        String basePackage = (ClassUtils.getPackageName(NeedDecrypt.class));
        scanner.scan(basePackage);
    }
}

3、实现

3.1、启动项注解

需要在Application启动类中添加生效注解,这样其他子服务中才能调用加密解密的方法,如下图所示

【Spring Cloud 指定敏感字段存储数据库加密】_第2张图片

 

 3.2、配置实体中需要加密的字段

在需要加密的实体字段上添加@EncryptField注解

【Spring Cloud 指定敏感字段存储数据库加密】_第3张图片

 3.3、加密

【Spring Cloud 指定敏感字段存储数据库加密】_第4张图片

 在方法上添加注解,这样实体存入数据时,会转到aop处理器判断哪些字段需要进行加密处理,判断依据为添加了@EncryptField注解的字段。

3.4、解密

【Spring Cloud 指定敏感字段存储数据库加密】_第5张图片

 本文控制层的返回参数是封装好的返回对象,所以改在service实现类中添加解密注解,这样aop处理器中接收到的是实体参数的list集合,同样根据循环判断哪些添加了@EncryptField注解的字段进行解密操作,然后返回集合。

三、使用MySQL加密解密函数

创建一张测试表,其中进行加密解密操作的字段类型需要设置成varbinary;

【Spring Cloud 指定敏感字段存储数据库加密】_第6张图片

 然后调用AES_ENCRYPT()和AES_DECRYPT()函数进行加密解密。

【Spring Cloud 指定敏感字段存储数据库加密】_第7张图片

 

你可能感兴趣的:(Spring,Cloud,#,密码学,spring,boot,加密解密,spring,cloud)