[Spring Cloud specifies sensitive fields to store database encryption]

1, Foreword

When doing the project, sometimes we will encounter the requirements of storing bank card number and mailbox information into the database for encryption. This paper uses jasypt for encryption. reference material:

Springboot AOP implements data encryption of specified sensitive fields (data encryption Chapter 2)

2, AOP encryption

maven reference

<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dep

  1. Encryption

1.1. User defined annotation of methods requiring encryption

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;

/**
 * Encryption required
 * @author sed
 * @date 2021-11-30 09:42:49
 * @Description
 *  The annotation is on the interface to identify that the interface needs to be encrypted and intercepted
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {
}

1.2. Custom annotation for fields to be encrypted

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;

/**
 * Field identification annotation to be encrypted
 * @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 processor for encryption logic

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 processor for encryption logic
 * @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{
        //encryption
        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 ){
                        //The judgment of other types of fields can be extended
                        objects[i] = encryptValue(objects[i]);
                    }else {
                        encryptObject(objects[i]);
                    }
                }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * Encrypted object
     * @param obj
     * @throws IllegalAccessException
     */
    private void encryptObject(Object obj) throws IllegalAccessException {

        if (Objects.isNull(obj)){
            log.info("Currently encrypted object by null");
            return;
        }
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields){
            boolean containEncryptField = field.isAnnotationPresent(EncryptField.class);
            if (containEncryptField){
                //Get access
                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("Encryption exception={}",e.getMessage());
        }
        return String.valueOf(realValue);
    }

    private String encrypt(String text){
        BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
        //Salt required for encryption
        textEncryptor.setPassword("EbfYkitulv73I2p0mXI50JMXoaxZTKJ7");
        return textEncryptor.encrypt(text);
    }
}

1.4. Encryption annotation effective start method

package com.sed.commons.sensitive.encrypt;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * Encryption annotation effective start method
 * @author sed
 * @date 2021-11-30 11:35:59
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EncryptRegister.class})
public @interface EnableEncrypt {
}

The above @ Import class in parentheses is the implementation class of ImportBeanDefinitionRegistrar, which is responsible for calling the interface method to register the class to be registered as a bean

1.5 effective configuration

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. Decryption

2.1. User defined annotation of the method to be decrypted

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;

/**
 * Decryption < p > is written in the implementation class</p>
 * @author sed
 * @date 2021-11-30 16:28:28
 * @Description
 *  The annotation is on the interface to identify that the interface needs to be decrypted and intercepted
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedDecrypt {
}

2.2 aop processor for decryption logic

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 processor for decrypting logic
 * @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 {
        //decrypt
        Object result = decrypt(joinPoint);
        return result;
    }

    public Object decrypt(ProceedingJoinPoint joinPoint) {
        Object result = null;
        try {
            Object obj = joinPoint.proceed();
            if (obj != null) {
                //You can extend the judgment of other types of fields by yourself
                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;
    }

    /**
     * Decrypt for a single entity class
     * @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);
            }
        }
    }

    /**
     * Reflect and decrypt the list < entity >
     * @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 = decrypt(String.valueOf(realValue));
        } catch (Exception e) {
            log.info("Decryption exception={}", e.getMessage());
        }
        return String.valueOf(realValue);
    }

    private String decrypt(String text){
        BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
        //Salt required for encryption
        textEncryptor.setPassword("EbfYkitulv73I2p0mXI50JMXoaxZTKJ7");
        return textEncryptor.decrypt(text);
    }
}

2.3. Decryption annotation effective start method

package com.sed.commons.sensitive.decrypt;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * Decryption annotation effective start method
 * @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 effective configuration

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. Realize

3.1 notes to startup items

An effective annotation needs to be added to the Application startup class so that the encryption and decryption methods can be called in other sub services, as shown in the following figure

 

  3.2. Configure the fields to be encrypted in the entity

Add @ EncryptField annotation on the entity field to be encrypted

  3.3 encryption

  Add annotation on the method, so that when the entity stores data, it will go to the aop processor to judge which fields need to be encrypted. The judgment is based on the fields with @ EncryptField annotation.

3.4 decryption

  In this paper, the return parameters of the control layer are encapsulated return objects, so the decryption annotation is added to the service implementation class. In this way, the aop processor receives the list set of entity parameters. Similarly, it judges which fields with @ EncryptField annotation are decrypted according to the loop, and then returns the set.

3, Use MySQL encryption and decryption function

Create a test table in which the field type for encryption and decryption needs to be set to varbinary;

  Then call AES_. Encrypt () and AES_ Encrypt and decrypt with decrypt() function.

 

Tags: Spring Boot Spring Cloud Encryption

Posted on Wed, 01 Dec 2021 09:00:59 -0500 by cedtech31