SpringDataJpa-字段加解密存储
- 背景
- 场景
- 实现
- 类型转换器
- 实体类修改
- Crypto 注解
- AOP
- 目前可使用场景
- 注意
背景
遇到一个需求,对数据库中的某些字段进行加密存储,但是在各个服务流转中,需要是解密状态的。框架使用的是JPA。
Spring 提供了 AttributeConverter<T,R> 用来将实体属性与数据库字段之间的逻辑转换。换日期格式转换这种。但是我们这边需要改变类型,只需要改变类型的值。
但是使用原生SQL的时候用加密字段进行查询的化,是不会使用类型转换器的。所以我这里增加了一个注解,来拦截标有该注解的参数,然后将参数的值进行修改,然后再进行查询。这里实现的化使用的是 AOP
场景
- 使用实体类进行查询,插入,更新,删除(
HQL
)- 使用加密字段进行查询的(使用
类型转换器
实现) - 返回为实体类 (使用
类型转换器
实现)
- 使用加密字段进行查询的(使用
- 使用
原生sql
进行查询,插入,更新,删除- 返回出来的是 map 结构(需要单独处理)
- 返回为实体类 (使用
类型转换器
实现) - 使用加密字段进行查询的(
AOP + 注解实现
)
实现
类型转换器
实现
AttributeConverter
@Slf4j
public class CryptoConvert implements AttributeConverter<String,String> {/*** 存入数据库前进行的转化操作* @param attribute* @return*/@Overridepublic String convertToDatabaseColumn(String attribute) {String encode = CryptoUtil.encode(attribute);log.info("oldValue:{},newValue:{}",attribute,encode);return encode;}/*** 取出数据库后进行的转化操作* @param dbData* @return*/@Overridepublic String convertToEntityAttribute(String dbData) {String decode = CryptoUtil.decode(dbData);log.info("oldValue:{},newValue:{}",dbData,decode);return decode;}
}
实体类修改
@Column(name="remark")
// 增加类型转换器,标注该字段需要使用类型转换器处理
@Convert(converter = CryptoConvert.class)
private java.lang.String remarks;
Crypto 注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Crypto {
}
AOP
尝试使用
@Before
但是不能修改参数的值,所以这里使用@Around
@Slf4j
@Component
@Aspect
public class CryptoAspect {/*** 针对带有@Crypto注解的参数进行加密* @param joinPoint* @return* @throws Throwable*/@Around(value = "execution(* *(..,@Crypto (String),..))")public Object encrypt(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Annotation[][] parameterAnnotations = signature.getMethod().getParameterAnnotations();Object[] oldArgs = joinPoint.getArgs();Object[] newArgs = Arrays.copyOf(oldArgs, oldArgs.length);for (int i = 0; i < newArgs.length; i++) {if (Objects.isNull(newArgs[i])) {continue;}if(!(newArgs[i] instanceof String)){continue;}Annotation[] parameterAnnotation = parameterAnnotations[i];if (ArrayUtils.isEmpty(parameterAnnotation)) {continue;}for (Annotation annotation : parameterAnnotation) {if (Crypto.class.equals(annotation.annotationType())) {String str = (String) newArgs[i];String encode = CryptoUtil.encode(str);newArgs[i] = encode;}}}log.info("oldArgs:{} newArgs:{}",oldArgs,newArgs);// 使用解密后的参数调用对应方法return joinPoint.proceed(newArgs);}
}
目前可使用场景
// 1.JPA 使用方法名操作数据库 remarks 为需要加密查询的字段
List<Model> findByRemarks(@Param(value = "remarks") String remarks);// 2.JPA 使用 @Query 注解并使用 HQL 操作数据库 remarks 为需要加密查询的字段
@Query("select C FROM Model C where C.remarks = ?1")
List<Model> findByRemarks2(@Param(value = "remarks") String remarks);// 3.JPA 使用 @Query 注解并使用 原生SQL 操作数据库 remarks 为需要加密查询的字段 (hibernate 查出数据库时也会使用类型转换器所以不再单独解密)
@Query(value = "select * from t_model where remark = ?1",nativeQuery = true)
List<Model> findByRemarks3(@Crypto @Param(value = "remarks") String remarks);// 4.使用 Query + HQL 及可实现(hibernate 查出数据库时也会使用类型转换器所以不再单独解密)
@Override
public List<Model> getRemarks2(String remarks) {Query query = em.createQuery("select C FROM Model C where C.remarks = ?1",AccountBindHistory.class);query.setParameter(1, remarks);List<Model> resultList = query.getResultList();return resultList;
}// 5.使用 Query + SQL 这个需要在入口处添加@Crypto 及可实现(hibernate 查出数据库时也会使用类型转换器所以不再单独解密)
@Override
public List<Model> getRemarks(@Crypto String remark) {Query query = em.createNativeQuery("select * from t_model where remark = ?",AccountBindHistory.class);query.setParameter(1, remark);List<Model> resultList = query.getResultList();return resultList;
}// em.createNativeQuery 在创建Query的时候需要指定操作的映射类型// 6.Spring-data-rest 生成的接口也支持自动加解密
注意
- 加密方式需要使用**对称加密,**也就是可以将明文转换出来的算法。
- 数据库中已有的数据需要进行处理,也就是需要加密。这样可以保证查询是与后入库的数据都能正常解密,