mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-10-31 10:57:15 +08:00 
			
		
		
		
	refactor(security/crypto): 支持 MyBatis 查询参数加密
This commit is contained in:
		| @@ -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 { | ||||
|  | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 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 { | ||||
|         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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user