com.ideaaedi.mybatis.data.security.support.EncryptParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mybatis-data-security Show documentation
Show all versions of mybatis-data-security Show documentation
Automatically encrypt and decrypt database data through mybatis-interceptor.
package com.ideaaedi.mybatis.data.security.support;
import com.ideaaedi.mybatis.data.security.annotation.Encrypt;
import com.ideaaedi.mybatis.data.security.enums.TypeEnum;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 加解密解析器
*
* @author JustryDeng
* @since 2021/2/11 11:41:24
*/
public class EncryptParser implements SmartInitializingSingleton {
private static final Logger log = LoggerFactory.getLogger(EncryptParser.class);
/** 临时的class-method缓存(启动项目完毕后,会清除) */
@SuppressWarnings("rawtypes")
private static final Map TMP_CLAZZ_METHOD_CACHE = new ConcurrentHashMap<>(64);
/** MappedStatementId-EncryptInfoHolder缓存 */
private final Map statementIdAndEncryptInfoCache = new ConcurrentHashMap<>(128);
private final List sqlSessionFactoryList;
private final EncryptOop encryptOop;
private final DecryptOop decryptOop;
public EncryptParser(@Autowired ApplicationContext applicationContext, @Autowired EncryptExecutor encryptExecutor) {
encryptOop = new EncryptOop(encryptExecutor);
decryptOop = new DecryptOop(encryptExecutor);
// 兼容低版本spring的写法
Map maps = applicationContext.getBeansOfType(SqlSessionFactory.class);
//noinspection ConstantConditions
if (maps == null) {
this.sqlSessionFactoryList = new ArrayList<>(1);
} else {
this.sqlSessionFactoryList = new ArrayList<>(maps.values());
}
}
/**
* 定位EncryptInfoHolder
*
* @param mappedStatementId
* sql对应的MappedStatement的id
* @return mappedStatementId对应的加解密信息详情类
*/
@Nullable
public EncryptInfoHolder determineEncryptInfoHolder(String mappedStatementId) {
return statementIdAndEncryptInfoCache.get(mappedStatementId);
}
/**
* 加密
*/
public Object doEncrypt(Object parameter, EncryptInfoHolder encryptInfoHolder) {
return encryptOop.doEncrypt(parameter, encryptInfoHolder);
}
/**
* 解密
*/
public Object doDecrypt(Object rowResult, EncryptInfoHolder encryptInfoHolder) {
return decryptOop.doDecrypt(rowResult, encryptInfoHolder);
}
public List getSqlSessionFactoryList() {
return sqlSessionFactoryList;
}
@Override
public void afterSingletonsInstantiated() {
// step0. 获取mybatis配置
StopWatch stopWatch = new StopWatch("encrypt parser");
stopWatch.start("get configuration");
List configurationList = sqlSessionFactoryList.stream().map(SqlSessionFactory::getConfiguration).collect(Collectors.toList());
log.info("[EncryptParser] find org.apache.ibatis.session.Configuration count {}.", configurationList.size());
stopWatch.stop();
// step1. 获取sql对应的MappedStatement的Id
stopWatch.start("get mappedStatementIdSet");
Set mappedStatementIdSet = configurationList.stream().flatMap(x -> {
Collection mappedStatements = x.getMappedStatements();
Object[] objects = mappedStatements.toArray();
List list = new ArrayList<>(16);
for (Object object : objects) {
if (object instanceof MappedStatement) {
list.add((MappedStatement) object);
}
}
return list.stream();
}).map(MappedStatement::getId).collect(Collectors.toSet());
// 过滤掉那些自动生成的方法,如:为回填主键而自动生成的sql,如:com.aspire.ssm.test.mapper.Abc.Mapper.insertData!selectKey
mappedStatementIdSet = mappedStatementIdSet.stream().filter(x -> !x.contains("!")).collect(Collectors.toSet());
log.info("[EncryptParser] find org.apache.ibatis.mapping.MappedStatement {}.", mappedStatementIdSet);
stopWatch.stop();
// step2. 获取sql对应的Method-MappedStatement的Id map
stopWatch.start("get methodStatementIdMap,methodSet");
Map methodStatementIdMap = mappedStatementIdSet.stream().collect(
Collectors.toMap(this::determineMethodByMappedStatementId, Function.identity()));
Set methodSet = methodStatementIdMap.keySet();
stopWatch.stop();
// step3. 获取对应的注解
stopWatch.start("get params,return info");
Map methodParamsMap = methodSet.stream().collect(Collectors.toMap(Function.identity(), Method::getParameters));
Map methodParamAnnotationMap = methodSet.stream().collect(Collectors.toMap(Function.identity(), Method::getParameterAnnotations));
Map> methodReturnMap = methodSet.stream().collect(Collectors.toMap(Function.identity(), Method::getReturnType));
stopWatch.stop();
// step4. 解析加密信息(其中,会对对已有代码进行一些强约束校验)
stopWatch.start("get encryptInfoHolderList");
List encryptInfoHolderList = methodSet.stream().map(method ->
EncryptInfoHolder.Factory.create(
methodStatementIdMap.get(method),
method,
methodParamsMap.get(method),
methodParamAnnotationMap.get(method),
methodReturnMap.get(method)
)
).collect(Collectors.toList());
stopWatch.stop();
// step5. 后处理收尾
stopWatch.start("do completely");
Map encryptDecryptInfoMap = encryptInfoHolderList.stream()
.collect(Collectors.toMap(EncryptInfoHolder::getMappedStatementId, Function.identity()));
// 存放加密信息
statementIdAndEncryptInfoCache.putAll(encryptDecryptInfoMap);
log.info("[EncryptParser] parse end. Obtain encryptDecryptInfoMap {}.", encryptDecryptInfoMap);
// 清除临时缓存
TMP_CLAZZ_METHOD_CACHE.clear();
stopWatch.stop();
log.info("[EncryptParser] time-consuming statistics {}.", stopWatch);
}
/**
* 获取mappedStatementId对应的方法
*
* @param mappedStatementId
* sql对应的MappedStatement的Id
* @return mappedStatementId对应的方法
*/
private Method determineMethodByMappedStatementId(String mappedStatementId) {
try {
int lastDotIndex = mappedStatementId.lastIndexOf(".");
Class> targetClass = Class.forName(mappedStatementId.substring(0, lastDotIndex));
Method[] declareMethods = TMP_CLAZZ_METHOD_CACHE.computeIfAbsent(targetClass, Class::getDeclaredMethods);
String targetMethodName = mappedStatementId.substring(lastDotIndex + 1);
List targetMethodList = Arrays.stream(declareMethods).filter(x -> targetMethodName.equals(x.getName())).collect(Collectors.toList());
int size = targetMethodList.size();
if (size != 1) {
throw new IllegalStateException(String.format("except find [%s] 1, but actual find %s", mappedStatementId, size));
}
return targetMethodList.get(0);
}catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
/**
* 加密相关方法
*
* 注:启动项目时,在{@link EncryptParser#afterSingletonsInstantiated()}中做了一些强约束,所以在运行时进行加密时,可以跳过一些没必要的校验,以提升性能
*
*
* @author JustryDeng
* @since 2021/2/11 16:55:24
*/
public static class EncryptOop {
private final EncryptExecutor encryptExecutor;
private EncryptOop(EncryptExecutor encryptExecutor) {
this.encryptExecutor = encryptExecutor;
}
/**
* 加密
*
* @param parameter
* 到mybatis插件时的参数。即:{@link Executor#update(MappedStatement, Object)}的第二个参数
* @param encryptInfoHolder
* 对应的加密信息
* @return 加密后的parameter
*/
public Object doEncrypt(Object parameter, EncryptInfoHolder encryptInfoHolder) {
if (parameter == null) {
return null;
}
TypeEnum typeEnum = TypeEnum.parseType(parameter.getClass());
if (typeEnum == TypeEnum.PRIMITIVE_OR_WRAPPER || typeEnum ==TypeEnum.STRING) {
// 当parameter是这两种场景时,是不需要加密的 (
// 提示:@Encrypt使用在ElementType.PARAMETER前时,已强制要求同时使用@Param注解指定名称,此时的parameter类型是Map而非直接是String)
return parameter;
}
List encryptBeanInfoList = encryptInfoHolder.getEncryptBeanInfoList();
EncryptInfoHolder.ParamEncryptDetailInfo encryptParamInfo = encryptInfoHolder.getEncryptParamInfo();
Object newParameter;
switch (typeEnum) {
case MAP:
//noinspection unchecked
newParameter = mapEncrypt((Map) parameter, encryptBeanInfoList, encryptParamInfo);
break;
case COLLECTION:
//noinspection unchecked
newParameter = collectionEncrypt((Collection