在做项目的时候,有的时候会遇到存储银行卡号、邮箱信息入数据库加密的要求,本文使用jasypt进行加密。参考资料:
Springboot AOP实现指定敏感字段数据加密 (数据加密篇 二)
com.github.ulisesbocchio
jasypt-spring-boot-starter
2.1.0
org.projectlombok
lombok
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 {
}
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 "";
}
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);
}
}
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
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);
}
}
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 {
}
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
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 {
}
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);
}
}
需要在Application启动类中添加生效注解,这样其他子服务中才能调用加密解密的方法,如下图所示
在需要加密的实体字段上添加@EncryptField注解
在方法上添加注解,这样实体存入数据时,会转到aop处理器判断哪些字段需要进行加密处理,判断依据为添加了@EncryptField注解的字段。
本文控制层的返回参数是封装好的返回对象,所以改在service实现类中添加解密注解,这样aop处理器中接收到的是实体参数的list集合,同样根据循环判断哪些添加了@EncryptField注解的字段进行解密操作,然后返回集合。
创建一张测试表,其中进行加密解密操作的字段类型需要设置成varbinary;
然后调用AES_ENCRYPT()和AES_DECRYPT()函数进行加密解密。