mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-10-31 22:57:19 +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