mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-09-27 18:59:24 +08:00
refactor(security/crypto): 支持 MyBatis 查询参数加密
This commit is contained in:
@@ -30,7 +30,7 @@ import java.lang.annotation.Target;
|
|||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 1.4.0
|
* @since 1.4.0
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.FIELD)
|
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface FieldEncrypt {
|
public @interface FieldEncrypt {
|
||||||
|
|
||||||
|
@@ -16,17 +16,21 @@
|
|||||||
|
|
||||||
package top.charles7c.continew.starter.security.crypto.core;
|
package top.charles7c.continew.starter.security.crypto.core;
|
||||||
|
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.plugin.*;
|
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.annotation.FieldEncrypt;
|
||||||
import top.charles7c.continew.starter.security.crypto.encryptor.IEncryptor;
|
import top.charles7c.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||||
import top.charles7c.continew.starter.security.crypto.enums.Algorithm;
|
import top.charles7c.continew.starter.security.crypto.enums.Algorithm;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.Arrays;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Collections;
|
import java.lang.reflect.Parameter;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字段解密拦截器
|
* 字段解密拦截器
|
||||||
@@ -36,6 +40,40 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public abstract class AbstractMyBatisInterceptor implements Interceptor {
|
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 加/解密处理器
|
* @return 加/解密处理器
|
||||||
*/
|
*/
|
||||||
public IEncryptor getEncryptor(Field field) {
|
public IEncryptor getEncryptor(FieldEncrypt fieldEncrypt) {
|
||||||
FieldEncrypt fieldEncrypt = field.getAnnotation(FieldEncrypt.class);
|
|
||||||
Class<? extends IEncryptor> encryptorClass = fieldEncrypt.encryptor();
|
Class<? extends IEncryptor> encryptorClass = fieldEncrypt.encryptor();
|
||||||
// 使用预定义加/解密处理器
|
// 使用预定义加/解密处理器
|
||||||
if (encryptorClass == IEncryptor.class) {
|
if (encryptorClass == IEncryptor.class) {
|
||||||
|
@@ -63,7 +63,7 @@ public class MyBatisDecryptInterceptor extends AbstractMyBatisInterceptor {
|
|||||||
List<Field> fieldList = super.getEncryptFields(result);
|
List<Field> fieldList = super.getEncryptFields(result);
|
||||||
// 解密处理
|
// 解密处理
|
||||||
for (Field field : fieldList) {
|
for (Field field : fieldList) {
|
||||||
IEncryptor encryptor = super.getEncryptor(field);
|
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
|
||||||
Object fieldValue = ReflectUtil.getFieldValue(result, field);
|
Object fieldValue = ReflectUtil.getFieldValue(result, field);
|
||||||
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
|
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
|
||||||
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class)
|
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class)
|
||||||
|
@@ -19,15 +19,21 @@ package top.charles7c.continew.starter.security.crypto.core;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||||
|
import org.apache.ibatis.cache.CacheKey;
|
||||||
import org.apache.ibatis.executor.Executor;
|
import org.apache.ibatis.executor.Executor;
|
||||||
|
import org.apache.ibatis.mapping.BoundSql;
|
||||||
import org.apache.ibatis.mapping.MappedStatement;
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
import org.apache.ibatis.mapping.SqlCommandType;
|
import org.apache.ibatis.mapping.SqlCommandType;
|
||||||
import org.apache.ibatis.plugin.*;
|
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.annotation.FieldEncrypt;
|
||||||
import top.charles7c.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
import top.charles7c.continew.starter.security.crypto.autoconfigure.CryptoProperties;
|
||||||
import top.charles7c.continew.starter.security.crypto.encryptor.IEncryptor;
|
import top.charles7c.continew.starter.security.crypto.encryptor.IEncryptor;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,7 +42,11 @@ import java.util.*;
|
|||||||
* @author Charles7c
|
* @author Charles7c
|
||||||
* @since 1.4.0
|
* @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 {
|
public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor {
|
||||||
|
|
||||||
private CryptoProperties properties;
|
private CryptoProperties properties;
|
||||||
@@ -52,21 +62,81 @@ public class MyBatisEncryptInterceptor extends AbstractMyBatisInterceptor {
|
|||||||
public Object intercept(Invocation invocation) throws Throwable {
|
public Object intercept(Invocation invocation) throws Throwable {
|
||||||
Object[] args = invocation.getArgs();
|
Object[] args = invocation.getArgs();
|
||||||
MappedStatement mappedStatement = (MappedStatement)args[0];
|
MappedStatement mappedStatement = (MappedStatement)args[0];
|
||||||
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
|
Object parameter = args[1];
|
||||||
if (!(SqlCommandType.UPDATE == sqlCommandType || SqlCommandType.INSERT == sqlCommandType)) {
|
if (!this.isEncryptRequired(parameter, mappedStatement.getSqlCommandType())) {
|
||||||
return invocation.proceed();
|
return invocation.proceed();
|
||||||
}
|
}
|
||||||
Object obj = args[1];
|
// 使用 @Param 注解的场景
|
||||||
// 兼容 MyBatis Plus 封装的 update 相关方法,updateById、update
|
if (parameter instanceof HashMap parameterMap) {
|
||||||
if (obj instanceof Map map) {
|
this.encryptMap(parameterMap, mappedStatement);
|
||||||
Object entity = map.get(Constants.ENTITY);
|
|
||||||
this.doEncrypt(this.getEncryptFields(entity), entity);
|
|
||||||
} else {
|
} else {
|
||||||
this.doEncrypt(this.getEncryptFields(obj), obj);
|
this.doEncrypt(this.getEncryptFields(parameter), parameter);
|
||||||
}
|
}
|
||||||
return invocation.proceed();
|
return invocation.proceed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要加密处理
|
||||||
|
*
|
||||||
|
* @param parameter 参数
|
||||||
|
* @param sqlCommandType SQL 类型
|
||||||
|
* @return true:是;false:否
|
||||||
|
*/
|
||||||
|
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 {
|
private void doEncrypt(List<Field> fieldList, Object entity) throws Exception {
|
||||||
for (Field field : fieldList) {
|
for (Field field : fieldList) {
|
||||||
IEncryptor encryptor = super.getEncryptor(field);
|
IEncryptor encryptor = super.getEncryptor(field.getAnnotation(FieldEncrypt.class));
|
||||||
Object fieldValue = ReflectUtil.getFieldValue(entity, field);
|
Object fieldValue = ReflectUtil.getFieldValue(entity, field);
|
||||||
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
|
// 优先获取自定义对称加密算法密钥,获取不到时再获取全局配置
|
||||||
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties
|
String password = ObjectUtil.defaultIfBlank(field.getAnnotation(FieldEncrypt.class).password(), properties
|
||||||
|
Reference in New Issue
Block a user