天天看點

Springboot AOP實作指定敏感字段資料加密 (資料加密篇 二)

前言

最近項目組開始關注一些敏感資料的明文相關的事宜 , 其實這些東西也是都有非常成熟的解決方案。 既然最近着手去解決這些事情,那麼也順便給還未了解的大夥普及一下。

這個系列就暫短的分成三篇 :

yml配置檔案裡敏感資料的加密

Springboot yml配置參數資料加密 (資料加密篇 一)_默默不代表沉默

傳入資料敏感資料的加密存儲

第三篇     使用mysql加解密函數輕松實作  

​​Springboot 使用mysql加密解密函數 (資料加密篇 三)_默默不代表沉默

本篇是第二篇 ,基于第一篇已經整合了jasypt 的基礎上 。

正文

内容:

1. 插入資料 自定義注解方式  對 指定接口方法 的 參數的指定字段進行 加密存儲;

2.對資料内的加密資料,進行解密傳回

先看看效果 : 

資料存入資料庫表内, 手機号phone和郵箱email 屬于敏感資料,我們需要密文存儲 : 

Springboot AOP實作指定敏感字段資料加密 (資料加密篇 二)

查詢解密傳回:

Springboot AOP實作指定敏感字段資料加密 (資料加密篇 二)

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 辨別:

Springboot AOP實作指定敏感字段資料加密 (資料加密篇 二)

ok,我們寫個測試接口,使用 @NeedEncrypt

Springboot AOP實作指定敏感字段資料加密 (資料加密篇 二)

使用postman調用一下測試接口:

Springboot AOP實作指定敏感字段資料加密 (資料加密篇 二)

 可以看下資料庫,資料已經加密存儲成功:

Springboot AOP實作指定敏感字段資料加密 (資料加密篇 二)

接下來是查詢解密環節:

解密這裡其實有些小講究。 因為查詢出來的資料有可能是單個實體,也可能是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);
    }


}      

然後我們對一個查詢方法進行測試 :

Springboot AOP實作指定敏感字段資料加密 (資料加密篇 二)

 我們先試一下查詢單條資料的:

使用@NeedDecrypt注解标記這個接口需要資料解密:

Springboot AOP實作指定敏感字段資料加密 (資料加密篇 二)

調用接口看看結果:

Springboot AOP實作指定敏感字段資料加密 (資料加密篇 二)

 然後是多條資料List<User> 傳回的接口: