1、加密工具
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
/**
* @author: xu.dm
* @since: 2018/11/24 22:26
*
*/
public class AesUtils {
private static final String ALGORITHM = "AES/ECB/PKCS5Padding";
public static String encrypt(String content, String key) {
try {
//获得密码的字节数组
byte[] raw = key.getBytes();
//根据密码生成AES密钥
SecretKeySpec keySpec = new SecretKeySpec(raw, "AES");
//根据指定算法ALGORITHM自成密码器
Cipher cipher = Cipher.getInstance(ALGORITHM);
//初始化密码器,第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作,第二个参数为生成的AES密钥
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
//获取加密内容的字节数组(设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
byte [] contentBytes = content.getBytes(StandardCharsets.UTF_8);
//密码器加密数据
byte [] encodeContent = cipher.doFinal(contentBytes);
//将加密后的数据转换为字符串返回
return Base64.encodeBase64String(encodeContent);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("AesUtils加密失败");
}
}
public static String decrypt(String encryptStr, String decryptKey) {
try {
//获得密码的字节数组
byte[] raw = decryptKey.getBytes();
//根据密码生成AES密钥
SecretKeySpec keySpec = new SecretKeySpec(raw, "AES");
//根据指定算法ALGORITHM自成密码器
Cipher cipher = Cipher.getInstance(ALGORITHM);
//初始化密码器,第一个参数为加密(ENCRYPT_MODE)或者解密(DECRYPT_MODE)操作,第二个参数为生成的AES密钥
cipher.init(Cipher.DECRYPT_MODE, keySpec);
//把密文字符串转回密文字节数组
byte [] encodeContent = Base64.decodeBase64(encryptStr);
//密码器解密数据
byte [] byteContent = cipher.doFinal(encodeContent);
//将解密后的数据转换为字符串返回
return new String(byteContent, StandardCharsets.UTF_8);
} catch (Exception e) {
// e.printStackTrace();
// 解密失败暂时返回null,可以抛出runtime异常
return null;
}
}
}
2、查询拦截器
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Properties;
/**
* @author: xu.dm
* @since: 2022/3/9 11:39
* 解密数据,返回结果为list集合时,应保证集合里都是同一类型的元素。
* 解密失败时返回为null,或者返回为空串时,不对原数据操作。
**/
@Component
@Slf4j
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})
public class DecryptionInterceptor implements Interceptor {
@Value("${AESKey}")
private String aesKey;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
if (result instanceof ArrayList) {
@SuppressWarnings("rawtypes")
ArrayList list = (ArrayList) result;
if (list.size() == 0) {
return result;
}
if (list.get(0) instanceof Encrypted) {
for (Object item : list) {
decryptField((Encrypted) item);
}
}
return result;
}
if (result instanceof Encrypted) {
decryptField((Encrypted) result);
}
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* @param object 待检查的对象
* @throws IllegalAccessException 通过查询注解@Encrypt或者Encrypted返回的字段,进行解密
* 两种方式互斥
*/
private void decryptField(Encrypted object) throws IllegalAccessException, NoSuchFieldException {
String[] encryptFields = object.getEncryptFields();
Class<?> clazz = object.getClass();
if (encryptFields.length == 0) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Encrypt encrypt = field.getAnnotation(Encrypt.class);
if (encrypt != null) {
if (field.get(object) != null && !StringUtils.isEmpty(field.get(object).toString())) {
String encryptString = AesUtils.decrypt(field.get(object).toString(), aesKey);
if (!StringUtils.isEmpty(encryptString)) {
field.set(object, encryptString);
log.debug("Encryption interceptor,encrypt field: {}", field.getName());
}
}
}
}
} else {
for (String fieldName : encryptFields) {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
if (field.get(object) != null && !StringUtils.isEmpty(field.get(object).toString())) {
String encryptString = AesUtils.decrypt(field.get(object).toString(), aesKey);
if (!StringUtils.isEmpty(encryptString)) {
field.set(object, encryptString);
log.debug("Encryption interceptor,encrypt field: {}", field.getName());
}
}
}
}
}
}
3、写操作拦截器
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* @author: xu.dm
* @since: 2022/3/8
* 拦截所有实现Encrypted接口的实体类insert和update操作
* 如果接口的getEncryptFields返回数组长度大于0,则使用该参数进行加密,
* 否则检查实体类中带@Encrypt注解,对该标识字段加密,
* 注意:待加密的字段最好是字符串,加密调用的是标识对象的ToString()结果进行加密,
**/
@Component
@Slf4j
@Intercepts({@Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class})})
public class EncryptionInterceptor implements Interceptor {
public EncryptionInterceptor() {
}
@Value("${AESKey}")
private String aesKey;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
SqlCommandType sqlCommandType = null;
for (Object object : args) {
// 从MappedStatement参数中获取到操作类型
if (object instanceof MappedStatement) {
MappedStatement ms = (MappedStatement) object;
sqlCommandType = ms.getSqlCommandType();
log.debug("Encryption interceptor 操作类型: {}", sqlCommandType);
continue;
}
log.debug("Encryption interceptor 操作参数:{}", object);
// 判断参数
if (object instanceof Encrypted) {
if (SqlCommandType.INSERT == sqlCommandType) {
encryptField((Encrypted) object);
continue;
}
if (SqlCommandType.UPDATE == sqlCommandType) {
encryptField((Encrypted) object);
log.debug("Encryption interceptor update operation,encrypt field: {}", object.toString());
}
}
// 兼容批量插入
if (object instanceof DefaultSqlSession.StrictMap) {
log.debug("mybatis arg: {}", object);
DefaultSqlSession.StrictMap map = (DefaultSqlSession.StrictMap) object;
String key = "collection";
if (!map.containsKey(key)) {
continue;
}
ArrayList objs = (ArrayList) map.get(key);
for (Object obj : objs) {
if (obj instanceof Encrypted) {
if (SqlCommandType.INSERT == sqlCommandType) {
encryptField((Encrypted) obj);
continue;
}
if (SqlCommandType.UPDATE == sqlCommandType) {
encryptField((Encrypted) obj);
log.debug("Encryption interceptor update operation,encrypt field: {}", obj.toString());
}
}
}
}
}
Object resObj = invocation.proceed();
for (Object object : args) {
// 从MappedStatement参数中获取到操作类型
if (object instanceof MappedStatement) {
MappedStatement ms = (MappedStatement) object;
sqlCommandType = ms.getSqlCommandType();
log.debug("Encryption interceptor 操作类型: {}", sqlCommandType);
continue;
}
log.debug("Encryption interceptor 操作参数:{}", object);
// 判断参数
if (object instanceof Encrypted) {
if (SqlCommandType.INSERT == sqlCommandType) {
decryptField((Encrypted) object);
continue;
}
if (SqlCommandType.UPDATE == sqlCommandType) {
decryptField((Encrypted) object);
log.debug("Encryption interceptor update operation,encrypt field: {}", object.toString());
}
}
// 兼容批量插入
if (object instanceof DefaultSqlSession.StrictMap) {
log.debug("mybatis arg: {}", object);
DefaultSqlSession.StrictMap map = (DefaultSqlSession.StrictMap) object;
String key = "collection";
if (!map.containsKey(key)) {
continue;
}
ArrayList objs = (ArrayList) map.get(key);
for (Object obj : objs) {
if (obj instanceof Encrypted) {
if (SqlCommandType.INSERT == sqlCommandType) {
decryptField((Encrypted) obj);
continue;
}
if (SqlCommandType.UPDATE == sqlCommandType) {
decryptField((Encrypted) obj);
log.debug("Encryption interceptor update operation,encrypt field: {}", obj.toString());
}
}
}
}
}
return resObj;
}
/**
* @param object 待检查的对象
* @throws IllegalAccessException 通过查询注解@Encrypt或者Encrypted返回的字段,进行动态加密
* 两种方式互斥
*/
private void encryptField(Encrypted object) throws IllegalAccessException, NoSuchFieldException {
String[] encryptFields = object.getEncryptFields();
Class<?> clazz = object.getClass();
if (encryptFields.length == 0) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Encrypt encrypt = field.getAnnotation(Encrypt.class);
if (encrypt != null) {
if (field.get(object) != null && !StringUtils.isEmpty(field.get(object).toString())) {
String encryptString = AesUtils.encrypt(field.get(object).toString(), aesKey);
if (!StringUtils.isEmpty(encryptString)) {
field.set(object, encryptString);
log.debug("Encryption interceptor,encrypt field: {}", field.getName());
}
}
}
}
} else {
for (String fieldName : encryptFields) {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
if (field.get(object) != null && !StringUtils.isEmpty(field.get(object).toString())) {
String encryptString = AesUtils.encrypt(field.get(object).toString(), aesKey);
if (!StringUtils.isEmpty(encryptString)) {
field.set(object, encryptString);
log.debug("Encryption interceptor,encrypt field: {}", field.getName());
}
}
}
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* @param object 待检查的对象
* @throws IllegalAccessException 通过查询注解@Encrypt或者Encrypted返回的字段,进行解密
* 两种方式互斥
*/
private void decryptField(Encrypted object) throws IllegalAccessException, NoSuchFieldException {
String[] encryptFields = object.getEncryptFields();
Class<?> clazz = object.getClass();
if (encryptFields.length == 0) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Encrypt encrypt = field.getAnnotation(Encrypt.class);
if (encrypt != null) {
if (field.get(object) != null && !StringUtils.isEmpty(field.get(object).toString())) {
String encryptString = AesUtils.decrypt(field.get(object).toString(), aesKey);
if (!StringUtils.isEmpty(encryptString)) {
field.set(object, encryptString);
log.debug("Encryption interceptor,encrypt field: {}", field.getName());
}
}
}
}
} else {
for (String fieldName : encryptFields) {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
if (field.get(object) != null && !StringUtils.isEmpty(field.get(object).toString())) {
String encryptString = AesUtils.decrypt(field.get(object).toString(), aesKey);
if (!StringUtils.isEmpty(encryptString)) {
field.set(object, encryptString);
log.debug("Encryption interceptor,encrypt field: {}", field.getName());
}
}
}
}
}
}
4、接口声明
/**
* @author: xu.dm
* @since: 2022/3/8 16:30
* 该接口用于标记实体类需要加密,具体的加密内容字段通过getEncryptFields返回.
* 注意:getEncryptFields与@Encrypt注解可配合使用也可以互斥使用,根据具体的需求实现。
**/
public interface Encrypted {
/**
* 实现该接口,返回需要加密的字段名数组,需与类中字段完全一致,区分大小写
* @return 返回需要加密的字段
*/
default String[] getEncryptFields() {
return new String[0];
}
}
5、注解声明
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author : xu.dm
* @since : 2022/3/8
* 标识加密的注解,value值暂时没用,根据需要可以考虑采用的加密方式与算法等
* 注意:Encrypted接口的getEncryptFields与@Encrypt注解可配合使用也可以互斥使用,根据具体的需求实现。
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypt {
String value() default "";
}
6、修改数据源
@Autowired(required = false)
private Interceptor[] interceptors;
@Bean
public SqlSessionFactory sqlSessionFactoryStatement() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(autohomestatement);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LLOCATIONS));
factoryBean.setTypeAliasesPackage(TYPE_ALIASES_PPACKAGE);
factoryBean.setPlugins(this.interceptors);
return factoryBean.getObject();
}
7、使用
public class StatementActivityAnswerDetailModifyBO implements Encrypted {
@Encrypt
private String personName;
}