com.ideaaedi.mybatis.data.security.support.EncryptInfoHolder Maven / Gradle / Ivy
Show all versions of mybatis-data-security Show documentation
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.reflect.TypeUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.ibatis.annotations.Param;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static com.ideaaedi.mybatis.data.security.enums.TypeEnum.ARRAY;
import static com.ideaaedi.mybatis.data.security.enums.TypeEnum.COLLECTION;
import static com.ideaaedi.mybatis.data.security.enums.TypeEnum.CUSTOM_BEAN;
import static com.ideaaedi.mybatis.data.security.enums.TypeEnum.MAP;
import static com.ideaaedi.mybatis.data.security.enums.TypeEnum.PRIMITIVE_OR_WRAPPER;
import static com.ideaaedi.mybatis.data.security.enums.TypeEnum.STRING;
/**
* 加解密信息持有器
*
* 本类虽然涉及有方法解析,但是由于非常简单,所以就自己写了。如果你需要全面解析java代码,但是你又没有时间或精力去自己写,那么你可以考虑使用这两款工具:
*
* - JavaParser:强大、全面,学习曲线较峭
* - QDox:基本够用,学习曲线较缓
*
*
*
* @author JustryDeng
* @since 2021/2/10 22:48:44
*/
public class EncryptInfoHolder {
private EncryptInfoHolder() {
}
/** sql对应的MappedStatement对象的Id */
private String mappedStatementId;
/** mappedStatementId对应的方法 */
private Method targetMethod;
/** 参数与参数的注解map */
private Map paramAnnotationMap;
/** 返回值类型 */
private Class> returnClass;
/** 是否需要加密 */
private boolean needEncrypt;
/** 要加密的bean信息 */
private List encryptBeanInfoList;
/** 要加密的参数信息 */
private ParamEncryptDetailInfo encryptParamInfo;
/** 是否需要解密 */
private boolean needDecrypt;
/** 要解密的bean信息 */
private BeanEncryptDetailInfo decryptBeanInfo;
public String getMappedStatementId() {
return mappedStatementId;
}
private void setMappedStatementId(String mappedStatementId) {
this.mappedStatementId = mappedStatementId;
}
public Method getTargetMethod() {
return targetMethod;
}
private void setTargetMethod(Method targetMethod) {
this.targetMethod = targetMethod;
}
public Map getParamAnnotationMap() {
return paramAnnotationMap;
}
private void setParamAnnotationMap(Map paramAnnotationMap) {
this.paramAnnotationMap = paramAnnotationMap;
}
public Class> getReturnClass() {
return returnClass;
}
private void setReturnClass(Class> returnClass) {
this.returnClass = returnClass;
}
public boolean isNeedEncrypt() {
return needEncrypt;
}
private void setNeedEncrypt(boolean needEncrypt) {
this.needEncrypt = needEncrypt;
}
public List getEncryptBeanInfoList() {
return encryptBeanInfoList;
}
private void setEncryptBeanInfoList(List encryptBeanInfoList) {
this.encryptBeanInfoList = encryptBeanInfoList;
}
public ParamEncryptDetailInfo getEncryptParamInfo() {
return encryptParamInfo;
}
private void setEncryptParamInfo(ParamEncryptDetailInfo encryptParamInfo) {
this.encryptParamInfo = encryptParamInfo;
}
public boolean isNeedDecrypt() {
return needDecrypt;
}
private void setNeedDecrypt(boolean needDecrypt) {
this.needDecrypt = needDecrypt;
}
public BeanEncryptDetailInfo getDecryptBeanInfo() {
return decryptBeanInfo;
}
private void setDecryptBeanInfo(BeanEncryptDetailInfo decryptBeanInfo) {
this.decryptBeanInfo = decryptBeanInfo;
}
@Override
public String toString() {
return "EncryptInfoHolder{" +
"mappedStatementId='" + mappedStatementId + '\'' +
", targetMethod=" + targetMethod +
", paramAnnotationMap=" + paramAnnotationMap +
", returnClass=" + returnClass +
", needEncrypt=" + needEncrypt +
", encryptBeanInfoList=" + encryptBeanInfoList +
", encryptParamInfo=" + encryptParamInfo +
", needDecrypt=" + needDecrypt +
", decryptBeanInfo=" + decryptBeanInfo +
'}';
}
/**
* bean的具体加密/解密信息
*/
public static class BeanEncryptDetailInfo {
private BeanEncryptDetailInfo() {
}
/** 字段-加解密信息 map */
private Map fieldEncryptMap;
/** 当前BeanEncryptDetailInfo对象所执行的bean的类型 */
private Class> beanClass;
/**
* 创建BeanEncryptDetailInfo实例
*
* @param beanClass
* 待解析的class
* @return BeanEncryptDetailInfo实例
*/
@NonNull
public static BeanEncryptDetailInfo build(Class> beanClass) {
BeanEncryptDetailInfo beanEncryptDetailInfo = new BeanEncryptDetailInfo();
beanEncryptDetailInfo.setBeanClass(beanClass);
beanEncryptDetailInfo.setFieldEncryptMap(extractBeanFieldEncryptInfo(beanClass));
return beanEncryptDetailInfo;
}
/**
* 解析beanClass中的@Encrypt注解
*
* @param beanClass
* 待解析的class
* @return class中的 字段-加密信息map
*/
public static Map extractBeanFieldEncryptInfo(Class> beanClass) {
Map infoMap = new HashMap<>(8);
ReflectionUtils.doWithFields(beanClass, (Field field) ->{
Encrypt annotation = field.getAnnotation(Encrypt.class);
if (annotation != null) {
// [强制约束] 当@Encrypt应用于ElementType.FIELD上时,对应的字段类型必须是String
Class> fieldType = field.getType();
if (TypeUtils.isAssignable(fieldType, String.class)) {
infoMap.put(field, annotation);
} else {
throw new IllegalArgumentException(
String.format("@Encrypt is not allowed to apply at type [%s], only for String,class. @see %s",
fieldType, beanClass.getName())
);
}
}
});
// 根据Encrypt的order值对map进行排序
return new ArrayList<>(infoMap.entrySet()).stream()
.sorted(Comparator.comparing(fieldEncryptEntry -> fieldEncryptEntry.getValue().order()))
.collect(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(u, v) -> {
throw new IllegalStateException("Duplicate key " + u);
},
LinkedHashMap::new
)
);
}
public Map getFieldEncryptMap() {
return fieldEncryptMap;
}
private void setFieldEncryptMap(Map fieldEncryptMap) {
this.fieldEncryptMap = fieldEncryptMap;
}
public Class> getBeanClass() {
return beanClass;
}
private void setBeanClass(Class> beanClass) {
this.beanClass = beanClass;
}
@Override
public String toString() {
return "BeanEncryptDetailInfo{" +
"fieldEncryptMap=" + fieldEncryptMap +
", beanClass=" + beanClass +
'}';
}
}
/**
* ElementType.PARAMETER 参数的具体加密=信息
*/
public static class ParamEncryptDetailInfo {
private ParamEncryptDetailInfo() {
}
public static final ParamEncryptDetailInfo EMPTY = new ParamEncryptDetailInfo();
/** ({@link Param}注解指定的)参数名 - 加解密信息 map */
private Map paramEncryptMap;
public Map getParamEncryptMap() {
return paramEncryptMap;
}
private void setParamEncryptMap(Map paramEncryptMap) {
this.paramEncryptMap = paramEncryptMap;
}
@Override
public String toString() {
return "ParamEncryptDetailInfo{" +
"paramEncryptMap=" + paramEncryptMap +
'}';
}
}
/**
* EncryptInfoHolder工厂
*/
public static class Factory {
private static final Logger log = LoggerFactory.getLogger(Factory.class);
/** 泛型左符号 */
private static final String GENERIC_LEFT_SIGN = "<";
/** 泛型右符号 */
private static final String GENERIC_RIGHT_SIGN = ">";
/** 获取方法签名的Method */
private static final Method GET_GENERIC_SIGNATURE;
static {
GET_GENERIC_SIGNATURE = ReflectionUtils.findMethod(Method.class, "getGenericSignature");
//noinspection ConstantConditions
GET_GENERIC_SIGNATURE.setAccessible(true);
}
/**
* 创建EncryptInfoHolder实例
*/
public static EncryptInfoHolder create(String mappedStatementId, Method targetMethod, Parameter[] parameters,
Annotation[][] paramAnnotations, Class> returnClass) {
EncryptInfoHolder instance = new EncryptInfoHolder();
instance.setMappedStatementId(mappedStatementId);
instance.setTargetMethod(targetMethod);
Map tmpMap = new HashMap<>(8);
for (int i = 0; i < parameters.length; i++) {
tmpMap.put(parameters[i], paramAnnotations[i]);
}
instance.setParamAnnotationMap(tmpMap);
instance.setReturnClass(returnClass);
Pair, ParamEncryptDetailInfo> encryptPair = analyzeEncryptDetailInfo(mappedStatementId, tmpMap);
instance.setEncryptBeanInfoList(encryptPair.getLeft());
instance.setEncryptParamInfo(encryptPair.getRight());
boolean needEncrypt = (!CollectionUtils.isEmpty(instance.getEncryptBeanInfoList()))
|| (!CollectionUtils.isEmpty(instance.getEncryptParamInfo().getParamEncryptMap()));
instance.setNeedEncrypt(needEncrypt);
BeanEncryptDetailInfo decryptBeanInfo = analyzeDecryptDetailInfo(targetMethod, returnClass);
instance.setDecryptBeanInfo(decryptBeanInfo);
boolean needDecrypt = decryptBeanInfo != null && (!CollectionUtils.isEmpty(decryptBeanInfo.fieldEncryptMap));
instance.setNeedDecrypt(needDecrypt);
log.debug("EncryptInfoHolder$Factory parse mappedStatementId [{}], create instance -> {}", mappedStatementId, instance);
return instance;
}
/**
* 分析加密信息详情
*
* @param mappedStatementId
* sql对应的mappedStatementId
* @param paramAnnotationMap
* 参数与注解map
* @return 加密信息详情
*
* - 左 - 对bean的具体加密信息
* - 右 - 对参数的具体加密信息
*
*/
private static Pair, ParamEncryptDetailInfo> analyzeEncryptDetailInfo(String mappedStatementId, Map paramAnnotationMap) {
List encryptBeanInfoList = new ArrayList<>(8);
ParamEncryptDetailInfo encryptParamInfo = new ParamEncryptDetailInfo();
Map paramEncryptMap = new HashMap<>(8);
encryptParamInfo.setParamEncryptMap(paramEncryptMap);
// 遍历参数进行解析
paramAnnotationMap.forEach((param, annotations) -> {
Class> type = param.getType();
TypeEnum typeEnum = TypeEnum.parseType(type);
// [强制约束] 当@Encrypt应用于ElementType.PARAMETER前时,参数项类型必须是String,不能是其它的
if (typeEnum != STRING) {
for (Annotation annotation : annotations) {
if (annotation instanceof Encrypt) {
throw new IllegalArgumentException(String.format("@Encrypt is not allowed to apply at type [%s]. @see %s",
type, mappedStatementId));
}
}
}
// 如果是基础类型或者是其包装类型就跳过
if (typeEnum == PRIMITIVE_OR_WRAPPER) {
return;
}
// string
if (typeEnum == STRING) {
Encrypt encryptAnnotation = (Encrypt)Arrays.stream(annotations).filter(x -> x instanceof Encrypt).findFirst().orElse(null);
Param paramAnnotation = (Param)Arrays.stream(annotations).filter(x -> x instanceof Param).findFirst().orElse(null);
// [强制约束] 当@Encrypt应用于ElementType.PARAMETER前时,还需同时使用@Param指定名称
if (encryptAnnotation != null) {
if (paramAnnotation == null) {
throw new IllegalArgumentException(String.format("While use @Encrypt in case ElementType.PARAMETER, "
+ "must use @Param at the same time. @see %s", mappedStatementId));
}
paramEncryptMap.put(paramAnnotation.value(), encryptAnnotation);
}
return;
}
// map、collection、array
if (typeEnum == MAP || typeEnum == COLLECTION || typeEnum == ARRAY) {
Pair> typeEnumClassPair = parseInnermostType(param, mappedStatementId);
if (typeEnumClassPair.getLeft() == CUSTOM_BEAN) {
BeanEncryptDetailInfo info = BeanEncryptDetailInfo.build(typeEnumClassPair.getRight());
if (!CollectionUtils.isEmpty(info.getFieldEncryptMap())) {
encryptBeanInfoList.add(info);
}
}
return;
}
if (typeEnum == CUSTOM_BEAN) {
BeanEncryptDetailInfo info = BeanEncryptDetailInfo.build(type);
if (!CollectionUtils.isEmpty(info.getFieldEncryptMap())) {
encryptBeanInfoList.add(info);
}
} else {
log.debug("Ignore analyze bean of type [{}]. @see {}", type.getName(), mappedStatementId);
}
});
return Pair.of(encryptBeanInfoList, encryptParamInfo);
}
/**
* 根据返回值类型,判断是否需要解密
*
* 理论支持:如果方法的参数或者返回值存在显示指定的泛型时,为了避免因泛型擦除而导致在某些场景下的定位混乱,所以JVM会为那些有泛型的方法生成签名,如:
*
* - 1. Map method1(@Param("name") String name);
*
* null 这种不指定具体泛型的,是不会生成签名的(即:获取到的签名为null).
*
* -
* 2. Map method2(@Param("p") Map
paramsMap);
*
* (Ljava/util/Map;)Ljava/util/Map;
*
* -
* 3. Map
method3(@Param("id") Integer id);
*
* (Ljava/lang/Integer;)Ljava/util/Map;
*
* -
* 4, int method4(@Param("p") Map
paramsMap);
*
* (Ljava/util/Map;)I
*
* -
* 5. List
* -
* 6. List
method6();
*
* ()Ljava/util/List;
*
* -
* 7. Employee[] method7(@Param("p") Map
paramsMap);
*
* (Ljava/util/Map;)[Lcom/aspire/ssm/model/Employee;
*
* -
* 8. Employee[][] method8(@Param("p") Map
paramsMap);
*
* (Ljava/util/Map;)[[Lcom/aspire/ssm/model/Employee;
*
* -
* 9。 Map method9(@Param("p1") Map
paramsMap1, @Param("p2") Map paramsMap2);
*
* (Ljava/util/Map;Ljava/util/Map;)Ljava/util/Map;
*
*
*
*
* 提示:简单的,可以使用以下代码输出类(如AbcMapper)中所有方法的签名
*
* Method getGenericSignature = ReflectionUtils.findMethod(Method.class, "getGenericSignature");
* getGenericSignature.setAccessible(true);
* Method[] declaredMethods = AbcMapper.class.getDeclaredMethods();
* for (Method declaredMethod : declaredMethods) {
* System.err.println(i + "\t" + ReflectionUtils.invokeMethod(getGenericSignature, declaredMethod));
* }
*
*
*/
@SuppressWarnings("AlibabaUndefineMagicConstant")
@Nullable
private static BeanEncryptDetailInfo analyzeDecryptDetailInfo(Method targetMethod, Class> returnClass){
TypeEnum typeEnum = TypeEnum.parseType(returnClass);
if (typeEnum == CUSTOM_BEAN) {
return BeanEncryptDetailInfo.build(returnClass);
}
if (typeEnum == MAP || typeEnum == COLLECTION) {
Object getGenericSignature = ReflectionUtils.invokeMethod(GET_GENERIC_SIGNATURE, targetMethod);
// 返回值没有明确的指定泛型,如上面的示例1
if (getGenericSignature == null) {
return null;
}
// 返回值没有明确的指定泛型,但由于参数存在泛型,导致getGenericSignature不为null的情况,如上面的示例2
String signature = getGenericSignature.toString();
String returnObjSignature = signature.substring(signature.lastIndexOf(")") + 1);
int startIndex = returnObjSignature.indexOf(GENERIC_LEFT_SIGN);
int endIndex = returnObjSignature.lastIndexOf(GENERIC_RIGHT_SIGN);
if (startIndex == -1 || endIndex == -1) {
return null;
}
// 此时,获取到的泛型,在上述示例3中体现为 Ljava/lang/String;Ljava/lang/Object;
// 此时,获取到的泛型,在上述示例5中体现为 Ljava/util/Map;
String genericInfo = returnObjSignature.substring(startIndex + 1, endIndex);
// [强制约束] 不支持泛型嵌套泛型这样的复杂写法(如上述示例5),快速失败,使项目启动不起来
if (genericInfo.contains(GENERIC_LEFT_SIGN) || genericInfo.contains(GENERIC_RIGHT_SIGN)) {
throw new UnsupportedOperationException(String.format("Cannot support return-type Generics nested Generics. @see %s", targetMethod));
}
// 逻辑到这里,已经保证了返回值的有泛型,且泛型一定是(类似于上述示例3那样的)非嵌套泛型
// Map的泛型是k-v的形式,要获取第二个; Collection直接获取第一个
int genericInfoIndex = typeEnum == MAP ? 1 : 0;
String onlyGenericInfo = genericInfo.split(";")[genericInfoIndex];
// [强制约束] 不允许泛型是数组,快速失败,使项目启动不起来
if (onlyGenericInfo.startsWith("[")) {
throw new UnsupportedOperationException(String.format("Cannot support return-type Generics exist Array. @see %s", targetMethod));
}
// (因为基本类型不能作为泛型且上面考虑了数组的情况,所以)到这里onlyGenericInfo就一定是L打头的了, L表示对象类型, 即:此时,onlyGenericInfo的值形如 Lcom/aspire/ssm/model/Employee
String elementClassName = onlyGenericInfo.substring(1).replace("/", ".");
Class> elementClass;
try {
elementClass = Class.forName(elementClassName);
}catch (ClassNotFoundException e) {
throw new IllegalStateException(String.format("Cannot find class [%s], while signature is [%s]. targetMethod is [%s].",
elementClassName, signature, targetMethod), e);
}
typeEnum = TypeEnum.parseType(elementClass);
if (typeEnum == CUSTOM_BEAN) {
return BeanEncryptDetailInfo.build(elementClass);
} else {
return null;
}
}
if (typeEnum == ARRAY) {
String clazzName = returnClass.getName();
// [强制约束] 返回值类型不能是多维数组
if (clazzName.startsWith("[[")) {
throw new UnsupportedOperationException(String.format("Cannot support return-type is multidimensional Array. @see %s", targetMethod));
}
if (!clazzName.startsWith("[L")) {
// 如果不是对象数组 (如:是基本类型数组)的话,直接返回null
return null;
}
// 逻辑到这里,即保证了返回值类型一定是一维数组,且是一维非基本类型数组
// 去掉开头的[L和结尾;号
clazzName = clazzName.substring(2, clazzName.length() - 1);
Class> beanClass;
try {
beanClass = Class.forName(clazzName);
typeEnum = TypeEnum.parseType(beanClass);
return typeEnum == CUSTOM_BEAN ? BeanEncryptDetailInfo.build(beanClass) : null;
} catch (ClassNotFoundException e) {
throw new IllegalStateException(String.format("Cannot find class [%s], targetMethod is [%s].",
clazzName, targetMethod), e);
}
}
return null;
}
/**
* 解析parameter的里层数据类型
*
* 示例:
*
* - Collection
=> 最里层的bean类型为E
* - Map
=> 最里层的bean类型为v
* - 一维数组[A] => 最里层的bean类型为A
* - ......
*
*
*
* @param parameter
* 待解析的参数信息
* @param mappedStatementId
* mappedStatementId可定位parameter所在方法的
* @return
* - 左 - 数据类型
* - 右 - 具体的class
*
* 注:此方法返回的TpeEnum,不可能为COLLECTION或MAP或ARRAY
*/
private static Pair> parseInnermostType(Parameter parameter, String mappedStatementId) {
Class> clazz = parameter.getType();
TypeEnum typeEnum = TypeEnum.parseType(clazz);
switch (typeEnum) {
case PRIMITIVE_OR_WRAPPER:
case STRING:
case CUSTOM_BEAN:
case SYSTEM_BEAN:
return Pair.of(typeEnum, clazz);
case COLLECTION:
case MAP:
Type parameterizedType = parameter.getParameterizedType();
if (parameterizedType instanceof ParameterizedTypeImpl) {
// 集合只有一个泛型,所以解析index=0的泛型;MAP解析value的类型,所以解析index=1的泛型
int targetTypeIndex = typeEnum == COLLECTION ? 0 : 1;
String genericClassName = ((ParameterizedTypeImpl) parameterizedType).getActualTypeArguments()[targetTypeIndex].getTypeName();
Class> genericClass;
try {
genericClass = Class.forName(genericClassName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(String.format("Cannot load class [%s]. At param [%s]. @see %s",
genericClassName, parameter.getName(), mappedStatementId));
}
TypeEnum genericTypeEnum = TypeEnum.parseType(genericClass);
// fail fast不支持复杂的泛型(如:泛型嵌套泛型等)
if (genericTypeEnum == MAP || genericTypeEnum == COLLECTION || genericTypeEnum == ARRAY) {
throw new UnsupportedOperationException(String.format("Cannot support parse complex Generics nested Generics. "
+ "At param[%s]. @see %s", parameter.getName(), mappedStatementId));
}
return Pair.of(genericTypeEnum, genericClass);
} else {
// fail fast不支持解析非ParameterizedTypeImpl类型的泛型
throw new UnsupportedOperationException(String.format("Cannot support parse non-ParameterizedTypeImpl Generics. "
+ "Maybe you should point specific generics at param[%s]. @see %s", parameter.getName(), mappedStatementId));
}
case ARRAY:
Type arrayComponentType = TypeUtils.getArrayComponentType(clazz);
if (arrayComponentType instanceof Class>) {
Class> arrayComponentClazz = (Class>)arrayComponentType;
TypeEnum arrayComponentTypeEnum = TypeEnum.parseType(arrayComponentClazz);
// fail fast不支持复杂的数组嵌套
if (arrayComponentTypeEnum == MAP || arrayComponentTypeEnum == COLLECTION || arrayComponentTypeEnum == ARRAY) {
throw new UnsupportedOperationException(String.format("Cannot support parse complex nesting Array. At param[%s]. @see %s",
parameter.getName(), mappedStatementId));
}
return Pair.of(arrayComponentTypeEnum, arrayComponentClazz);
} else {
// fail fast不支持解析非Class的实现
throw new UnsupportedOperationException(String.format("Cannot support parse non-Class Type. "
+ "At param[%s]. @see %s", parameter.getName(), mappedStatementId));
}
default:
throw new UnsupportedOperationException(String.format("Cannot support for typeEnum [%s] At param [%s]. @see %s",
typeEnum, parameter.getName(), mappedStatementId));
}
}
}
}