refactor(security/crypto): 支持 MyBatis 查询参数加密

This commit is contained in:
2024-02-09 09:55:52 +08:00
parent 5ccdd9e5da
commit 88f82d1c0a
4 changed files with 125 additions and 18 deletions

View File

@@ -30,7 +30,7 @@ import java.lang.annotation.Target;
* @author Charles7c
* @since 1.4.0
*/
@Target(ElementType.FIELD)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldEncrypt {

View File

@@ -16,17 +16,21 @@
package top.charles7c.continew.starter.security.crypto.core;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.plugin.*;
import top.charles7c.continew.starter.core.constant.StringConstants;
import top.charles7c.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.charles7c.continew.starter.security.crypto.encryptor.IEncryptor;
import top.charles7c.continew.starter.security.crypto.enums.Algorithm;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Stream;
/**
* 字段解密拦截器
@@ -36,6 +40,40 @@ import java.util.List;
*/
public abstract class AbstractMyBatisInterceptor implements Interceptor {
/**
* 获取参数列表
*
* @param mappedStatementId 映射语句 ID
* @return 参数列表
* @throws ClassNotFoundException /
*/
public Parameter[] getParameters(String mappedStatementId) throws ClassNotFoundException {
String className = CharSequenceUtil.subBefore(mappedStatementId, StringConstants.DOT, true);
String wrapperMethodName = CharSequenceUtil.subAfter(mappedStatementId, StringConstants.DOT, true);
String methodName = Stream.of("_mpCount", "_COUNT")
.filter(wrapperMethodName::endsWith)
.findFirst()
.map(suffix -> wrapperMethodName.substring(0, wrapperMethodName.length() - suffix.length()))
.orElse(wrapperMethodName);
Optional<Method> methodOptional = Arrays.stream(ReflectUtil.getMethods(Class.forName(className), m -> Objects
.equals(m.getName(), methodName))).findFirst();
if (methodOptional.isPresent()) {
return methodOptional.get().getParameters();
}
return new Parameter[0];
}
/**
* 获取参数名称
*
* @param parameter 参数
* @return 参数名称
*/
public String getParameterName(Parameter parameter) {
Param param = parameter.getAnnotation(Param.class);
return null != param ? param.value() : parameter.getName();
}
/**
* 获取所有字符串类型、需要加/解密的、有值字段
*
@@ -56,11 +94,10 @@ public abstract class AbstractMyBatisInterceptor implements Interceptor {
/**
* 获取字段加/解密处理器
*
* @param field 字段
* @param fieldEncrypt 字段加密注解
* @return 加/解密处理器
*/
public IEncryptor getEncryptor(Field field) {
FieldEncrypt fieldEncrypt = field.getAnnotation(FieldEncrypt.class);
public IEncryptor getEncryptor(FieldEncrypt fieldEncrypt) {
Class<? extends IEncryptor> encryptorClass = fieldEncrypt.encryptor();
// 使用预定义加/解密处理器
if (encryptorClass == IEncryptor.class) {

View File

@@ -63,7 +63,7 @@ public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor {
List<Field> fieldList = super.getEncryptFields(result);
// 解密处理
for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field);
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(result, field);
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class)

View File

@@ -19,15 +19,21 @@ package top.charles7c.continew.starter.security.crypto.core;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.toolkit.Constants;
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.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.SimpleTypeRegistry;
import top.charles7c.continew.starter.security.crypto.annotation.FieldEncrypt;
import top.charles7c.continew.starter.security.crypto.autoconfigure.CryptoProperties;
import top.charles7c.continew.starter.security.crypto.encryptor.IEncryptor;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.util.*;
/**
@@ -36,7 +42,11 @@ import java.util.*;
* @author Charles7c
* @since 1.4.0
*/
@Intercepts({@Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class}),})
@Intercepts({@Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class}),
@Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class})})
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor {
private CryptoProperties properties;
@@ -52,21 +62,81 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement)args[0];
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
if (!(SqlCommandType.UPDATE == sqlCommandType || SqlCommandType.INSERT == sqlCommandType)) {
Object parameter = args[1];
if (!this.isEncryptRequired(parameter, mappedStatement.getSqlCommandType())) {
return invocation.proceed();
}
Object obj = args[1];
// 兼容 MyBatis Plus 封装的 update 相关方法updateById、update
if (obj instanceof Map map) {
Object entity = map.get(Constants.ENTITY);
this.doEncrypt(this.getEncryptFields(entity), entity);
// 使用 @Param 注解的场景
if (parameter instanceof HashMap parameterMap) {
this.encryptMap(parameterMap, mappedStatement);
} else {
this.doEncrypt(this.getEncryptFields(obj), obj);
this.doEncrypt(this.getEncryptFields(parameter), parameter);
}
return invocation.proceed();
}
/**
* 是否需要加密处理
*
* @param parameter 参数
* @param sqlCommandType SQL 类型
* @return truefalse
*/
private boolean isEncryptRequired(Object parameter, SqlCommandType sqlCommandType) {
if (ObjectUtil.isEmpty(parameter)) {
return false;
}
if (!(SqlCommandType.UPDATE == sqlCommandType || SqlCommandType.INSERT == sqlCommandType || SqlCommandType.SELECT == sqlCommandType)) {
return false;
}
return !SimpleTypeRegistry.isSimpleType(parameter.getClass());
}
/**
* 加密 Map 类型数据
*
* @param parameterMap 参数
* @param mappedStatement 映射语句
* @throws Exception /
*/
private void encryptMap(HashMap<String, Object> parameterMap, MappedStatement mappedStatement) throws Exception {
Parameter[] parameterArr = super.getParameters(mappedStatement.getId());
for (int i = 0; i < parameterArr.length; i++) {
Parameter parameter = parameterArr[i];
String parameterName = super.getParameterName(parameter);
FieldEncrypt fieldEncrypt = parameter.getAnnotation(FieldEncrypt.class);
if (null != fieldEncrypt) {
parameterMap.put(parameterName, this.doEncrypt(parameterMap.get(parameterName), fieldEncrypt));
if (String.class.equals(parameter.getType())) {
String parameterIndexName = "param" + (i + 1);
parameterMap.put(parameterIndexName, this.doEncrypt(parameterMap
.get(parameterIndexName), fieldEncrypt));
}
} else if (parameterName.startsWith(Constants.ENTITY)) {
// 兼容 MyBatis Plus 封装的 update 相关方法updateById、update
Object entity = parameterMap.getOrDefault(Constants.ENTITY, null);
this.doEncrypt(this.getEncryptFields(entity), entity);
}
}
}
/**
* 处理加密
*
* @param parameterValue 参数值
* @param fieldEncrypt 字段加密注解
* @throws Exception /
*/
private Object doEncrypt(Object parameterValue, FieldEncrypt fieldEncrypt) throws Exception {
if (null == parameterValue) {
return null;
}
IEncryptor encryptor = super.getEncryptor(fieldEncrypt);
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(fieldEncrypt.password(), properties.getPassword());
return encryptor.encrypt(parameterValue.toString(), password, properties.getPublicKey());
}
/**
* 处理加密
*
@@ -76,7 +146,7 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor {
*/
private void doEncrypt(List<Field> fieldList, Object entity) throws Exception {
for (Field field : fieldList) {
IEncryptor encryptor = super.getEncryptor(field);
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
Object fieldValue = ReflectUtil.getFieldValue(entity, field);
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties