前言
最近项目组开始关注一些敏感数据的明文相关的事宜 , 其实这些东西也是都有非常成熟的解决方案。 既然最近着手去解决这些事情,那么也顺便给还未了解的大伙普及一下。
这个系列就暂短的分成三篇 :
第一篇 yml配置文件里敏感数据的加密
Springboot yml配置参数数据加密 (数据加密篇 一)_默默不代表沉默-CSDN博客
第二篇 传入数据敏感数据的加密存储
第三篇 使用mysql加解密函数轻松实现
Springboot 使用mysql加密解密函数 (数据加密篇 三)_默默不代表沉默-CSDN博客
本篇是第二篇 ,基于第一篇已经整合了jasypt 的基础上 。
正文
内容:
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> 返回的接口:
调用接口测试看看结果: