1.创建自定义注解类,该注解标注在类上
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SensitiveData {
}
2,创建自定义字段注解,该注解标注在字段上
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SensitiveField {
}
3.引入项目需要的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.11.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--aes 使用Base64加密时需要用到-->
<dependency>
<groupId>org.apache.directory.studio</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>
</dependencies>
4.yml配置文件
server:
port: 8890
spring:
application:
name: quartz-demo
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8&useSSL=false&ServerTimeZone=Asia/ShangHai
#batch: 注释掉与batch相关的配置
#jdbc:
#initialize-schema: always
#job:
#enabled: true
mybatis-plus:
mapper-locations: classpath:mapper/*Mapper.xml
type-aliases-package: com.quartz.demo.domain
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
aes:
key: sddwllwwasereddsssdfefdffeerwwsff
5.创建加密接口,之所以创建接口是为了后面加密的拓展,该文章中涉及到的加解密都是使用AES进行加解密
public interface EncryptService {
/**
* 加密
*
* @param declaredFields paramsObject所声明的字段
* @param paramsObject mapper中paramsType的实例
* @return T
* @throws IllegalAccessException 字段不可访问异常
*/
<T> T encrypt(Field[] declaredFields, T paramsObject) throws Exception;
}
6.加密接口实现了类
import com.quartz.demo.annotation.SensitiveField;
import com.quartz.demo.service.EncryptService;
import com.quartz.demo.utils.AESUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Objects;
@Component
public class AesEncryptServiceImpl implements EncryptService {
private AESUtils aesUtils;
@Value("${aes.key}")
private String key;
@Autowired
public AesEncryptServiceImpl(AESUtils aesUtils) {
this.aesUtils = aesUtils;
}
@Override
public <T> T encrypt(Field[] declaredFields, T paramsObject) throws Exception {
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
if (!Objects.isNull(sensitiveField)) {
field.setAccessible(true);
Object object = field.get(paramsObject);
//暂时只实现String类型的加密
if (object instanceof String) {
String value = (String) object;
//加密 这里我使用自定义的AES加密工具
field.set(paramsObject, aesUtils.aesEncrypt(value,key));
}
}
}
return paramsObject;
}
}
7.解密接口
public interface DecryptService {
/**
* 解密
*
* @param result resultType的实例
* @return T
* @throws IllegalAccessException 字段不可访问异常
*/
<T> T decrypt(T result) throws Exception;
}
7.解密接口实现类
import com.quartz.demo.annotation.SensitiveField;
import com.quartz.demo.service.DecryptService;
import com.quartz.demo.utils.AESUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Objects;
@Component
public class AESDecryptServiceImpl implements DecryptService {
private AESUtils aesUtils;
@Value("${aes.key}")
private String key;
@Autowired
public AESDecryptServiceImpl(AESUtils aesUtils) {
this.aesUtils = aesUtils;
}
@Override
public <T> T decrypt(T result) throws Exception {
//取出resultType的类
Class<?> resultClass = result.getClass();
Field[] declaredFields = resultClass.getDeclaredFields();
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
if (!Objects.isNull(sensitiveField)) {
field.setAccessible(true);
Object object = field.get(result);
//只支持String的解密
if (object instanceof String) {
String value = (String) object;
//对注解的字段进行逐一解密
field.set(result, aesUtils.aesDecrypt(value,key));
}
}
}
return result;
}
}
8.AES加解密工具类
import org.springframework.stereotype.Component;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
/**
* 功能:AES 工具类
* 说明:
* @author Mr.Zheng
* @date 2020-5-20 11:25
*/
@Component
public class AESUtils {
public String aesDecrypt(String encryptStr, String secretKey) throws Exception {
byte[] decodeBytes = new BASE64Decoder().decodeBuffer(encryptStr);
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(secretKey.getBytes());
keyGenerator.init(128, random);
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyGenerator.generateKey().getEncoded(), "AES"));
byte[] decryptBytes = cipher.doFinal(decodeBytes);
return new String(decryptBytes);
}
public String aesEncrypt(String originStr, String secretKey) throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(secretKey.getBytes());
keyGenerator.init(128, random);
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyGenerator.generateKey().getEncoded(), "AES"));
byte[] encodeBytes = cipher.doFinal(originStr.getBytes("utf-8"));
String encryptStr = new BASE64Encoder().encode(encodeBytes);
return encryptStr;
}
}
9.对于数据的加密与解密,应当存在两个拦截器对数据进行拦截操作参照官方文档,因此此处我们应当使用ParameterHandler拦截器对入参进行加密使用ResultSetHandler拦截器对出参进行解密操作,因此我们需要定义拦截器来处理数据,mybatis的interceptor接口有以下方法需要实现
public interface Interceptor {
//主要参数拦截方法
Object intercept(Invocation invocation) throws Throwable;
//mybatis插件链
default Object plugin(Object target) {return Plugin.wrap(target, this);}
//自定义插件配置文件方法
default void setProperties(Properties properties) {}
}
10.定义入参处理拦截器
import com.quartz.demo.annotation.SensitiveData;
import com.quartz.demo.service.EncryptService;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Objects;
import java.util.Properties;
@Component
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class EncryptInterceptor implements Interceptor {
private EncryptService encryptService;
@Autowired
public EncryptInterceptor(EncryptService encryptService) {
this.encryptService = encryptService;
}
public Object intercept(Invocation invocation) throws Throwable {
//@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
//若指定ResultSetHandler ,这里则能强转为ResultSetHandler
ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
// 获取参数对像,即 mapper 中 paramsType 的实例
Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
parameterField.setAccessible(true);
//取出实例
Object parameterObject = parameterField.get(parameterHandler);
if (parameterObject != null) {
Class<?> parameterObjectClass = parameterObject.getClass();
//校验该实例的类是否被@SensitiveData所注解
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
if (Objects.nonNull(sensitiveData)) {
//取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
encryptService.encrypt(declaredFields, parameterObject);
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Interceptor.super.plugin(target);
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}
11.定义出参(mybatis查询返回的结果)拦截器
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.quartz.demo.annotation.SensitiveData;
import com.quartz.demo.service.DecryptService;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;
@Component
@Intercepts(
@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = {Statement.class})
)
public class DecryptInterceptor implements Interceptor {
private DecryptService decryptService;
@Autowired
public DecryptInterceptor(DecryptService decryptService) {
this.decryptService = decryptService;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
//取出查询的结果
Object resultObject = invocation.proceed();
if (Objects.isNull(resultObject)) {
return null;
}
//基于selectList
if (resultObject instanceof ArrayList) {
ArrayList resultList = (ArrayList) resultObject;
if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
for (Object result : resultList) {
//逐一解密
decryptService.decrypt(result);
}
}
//基于selectOne
} else {
if (needToDecrypt(resultObject)) {
decryptService.decrypt(resultObject);
}
}
return resultObject;
}
private boolean needToDecrypt(Object object) {
Class<?> objectClass = object.getClass();
SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
return Objects.nonNull(sensitiveData);
}
@Override
public Object plugin(Object target) {
return Interceptor.super.plugin(target);
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}
12.拦截器参数讲解
@Intercepts 注解开启拦截器,@Signature 注解定义拦截器的实际类型。
@Signature中
-
type 属性指定当前拦截器使用StatementHandler 、ResultSetHandler、ParameterHandler,Executor的一种
-
method 属性指定使用以上四种类型的具体方法(可进入class内部查看其方法)。
-
args 属性指定预编译语句
13.使用自定义注解注解需要加密的字段
import com.quartz.demo.annotation.SensitiveData;
import com.quartz.demo.annotation.SensitiveField;
@SensitiveData
public class User {
private Long id;
private String userName;
@SensitiveField
private String password;
@SensitiveField
private String telephone;
public User() {
}
public User(Long id, String userName, String password, String telephone) {
this.id = id;
this.userName = userName;
this.password = password;
this.telephone = telephone;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
", telephone='" + telephone + '\'' +
'}';
}
}
13.测试验证加密效果,因为数据最终是保存在数据库里面的,所以只需要看新增后User中的加密字段有没有加密即可
14.解密后的查询结果,加密数据可以显示解密后的数据即可