1、依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>5.5.4</version>
</dependency>
2、秘钥配置类,配置信息写到properties或yaml文件里即可,或者直接写死,16位即可: encrypt.decrypt.key= 8sios12oap90sdks。
package com.common.encryptdecrypt;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class EncryptDecryptConfig {
@Value("${encrypt.decrypt.key}")
private String key;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
2、加密工具类
package com.common.encryptdecrypt;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @author
* @date 2022/8/4 18:18
* @describe
*/
public class EncryptDecryptUtil {
private final static Logger logger = LoggerFactory.getLogger(EncryptDecryptUtil.class);
/**
* 加密
*
* @param declaredFields paramsObject所声明的字段
* @param paramsObject mapper中paramsType的实例
* @return T
* @throws IllegalAccessException 字段不可访问异常
*/
public static <T> T encrypt(Field[] declaredFields, T paramsObject,String key) throws Exception {
byte[] KEYS = key.getBytes(StandardCharsets.UTF_8);
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
EncryptDecryptField sensitiveField = field.getAnnotation(EncryptDecryptField.class);
if (!Objects.isNull(sensitiveField)) {
field.setAccessible(true);
Object object = field.get(paramsObject);
//暂时只实现String类型的加密
if (object instanceof String) {
String value = (String) object;
//加密 Des加密工具
AES aes = SecureUtil.aes(KEYS);
String encrypt = aes.encryptHex(value);
field.set(paramsObject, encrypt);
}
}
}
return paramsObject;
}
/**
* 加密
* @param paramsObject
* @param key
* @param <T>
* @return
* @throws Exception
*/
public static <T> T encrypt(T paramsObject,String key) throws Exception {
Class<?>clazz=paramsObject.getClass();
//校验该实例的类是否被@EncryptDecryptData所注解
EncryptDecryptData encryptDecryptData = AnnotationUtils.findAnnotation(clazz, EncryptDecryptData.class);
if (Objects.nonNull(encryptDecryptData)) {
//取出当前当前类所有字段,传入加密方法
Field[] declaredFields = clazz.getDeclaredFields();
paramsObject = encrypt(declaredFields, paramsObject, key);
}
return paramsObject;
}
/**
* 解密
*
* @param result resultType的实例
* @return T
* @throws IllegalAccessException 字段不可访问异常
*/
public static <T> T decrypt(T result,String key) throws IllegalAccessException {
byte[] KEYS = key.getBytes(StandardCharsets.UTF_8);
//取出resultType的类
Class<?> resultClass = result.getClass();
Field[] declaredFields = resultClass.getDeclaredFields();
for (Field field : declaredFields) {
//取出所有被EncryptDecryptField注解的字段
EncryptDecryptField sensitiveField = field.getAnnotation(EncryptDecryptField.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, AESCryptos.aesDecrypt(value,key));
try {
field.set(result, SecureUtil.aes(KEYS).decryptStr(value));
} catch (IllegalAccessException e) {
// logger.debug(value + " 设置失败。",e);
} catch (CryptoException ex) {
logger.warn(value+" 非加密数据,无需解密");
//logger.debug(value + " 解密失败。",ex);
}catch (RuntimeException e){
logger.warn(value+"运行时异常");
}
}
}
}
return result;
}
/**
* 解密
* @param value
* @param key
* @return
*/
public static String decrypt(String value,String key){
try {
byte[] KEYS = key.getBytes(StandardCharsets.UTF_8);
value=SecureUtil.aes(KEYS).decryptStr(value);
} catch (CryptoException ex) {
logger.warn(value+" 非加密数据,无需解密");
//logger.debug(value + " 解密失败。",ex);
}catch (RuntimeException e){
logger.warn(value+"运行时异常");
}
return value;
}
}
3、自定义注解
package com.common.encryptdecrypt;
import java.lang.annotation.*;
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptData {
}
package com.common.encryptdecrypt;
import java.lang.annotation.*;
@Inherited
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {
}
4、拦截器
package com.common.encryptdecrypt;
import org.aopalliance.intercept.Interceptor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class ReadInterceptor implements Interceptor, org.apache.ibatis.plugin.Interceptor {
@Autowired
private EncryptDecryptConfig encryptDecryptConfig;
@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) {
//逐一解密
EncryptDecryptUtil.decrypt(result,encryptDecryptConfig.getKey());
}
}
//基于selectOne
} else {
if (needToDecrypt(resultObject)) {
EncryptDecryptUtil.decrypt(resultObject,encryptDecryptConfig.getKey());
}
}
return resultObject;
}
private boolean needToDecrypt(Object object) {
Class<?> objectClass = object.getClass();
EncryptDecryptData sensitiveData = AnnotationUtils.findAnnotation(objectClass, EncryptDecryptData.class);
return Objects.nonNull(sensitiveData);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
package com.common.encryptdecrypt;
import org.aopalliance.intercept.Interceptor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Objects;
import java.util.Properties;
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class WriteInterceptor implements Interceptor, org.apache.ibatis.plugin.Interceptor {
private final static Logger logger = LoggerFactory.getLogger(WriteInterceptor.class);
@Autowired
private EncryptDecryptConfig encryptDecryptConfig;
@Override
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();
logger.info("==========================parameterObjectClass = "+parameterObjectClass);
//校验该实例的类是否被@EncryptDecryptData所注解
EncryptDecryptData encryptDecryptData = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptData.class);
if (Objects.nonNull(encryptDecryptData)) {
//取出当前当前类所有字段,传入加密方法
Field[] declaredFields = parameterObjectClass.getDeclaredFields();
EncryptDecryptUtil.encrypt(declaredFields, parameterObject,encryptDecryptConfig.getKey());
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
//这里必须写入,会判定是否把当前拦截器启动
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
}
}
5、拦截器配置(该配置类主要是解决项目中可能存在其他拦截器,导致拦截器不起作用),可以在WriteInterceptor及ReadInterceptor类上加@Component注解,若拦截器起了作用,则不需要添加InterceptorConfig类。
package com.common.encryptdecrypt;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
public class InterceptorConfig {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Bean
public ReadInterceptor readInterceptor(){
return new ReadInterceptor();
}
@Bean
public WriteInterceptor writeInterceptor(){
return new WriteInterceptor();
}
@PostConstruct
public void addInterceptor(){
sqlSessionFactory.getConfiguration().addInterceptor(readInterceptor());
sqlSessionFactory.getConfiguration().addInterceptor(writeInterceptor());
}
}
6、加密示例(在需要加解密的实体类上加上@EncryptDecryptData注解,需要加密的字段上加@EncryptDecryptField注解即可)
package com.common.encryptdecrypt;
@EncryptDecryptData
public class Entity {
@EncryptDecryptField
private String needEncrypt;//需要加解密的字段
private String name;//不需要加解密的字段
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNeedEncrypt() {
return needEncrypt;
}
public void setNeedEncrypt(String needEncrypt) {
this.needEncrypt = needEncrypt;
}
}
7、注意点: 该拦截器在部分情况不能做到自动加解密(如返回值使用了Map接收),可以对其进行手动的加解密操作,也可以自我完善一下拦截器。
@Autowired
private EncryptDecryptConfig encryptDecryptConfig;
//对实体类进行加密
UserInfo userInfo=new UserInfo();
userInfo=EncryptDecryptUtil.encrypt(userInfo,encryptDecryptConfig.getKey());
//对字段进行解密
String str=EncryptDecryptUtil.decrypt(str,encryptDecryptConfig.getKey());
8、若出现 java.security.InvalidKeyException: Illegal key size。异常,则参考java.security.InvalidKeyException: Illegal key size_dling8的博客-CSDN博客_illegal key size
参考: