mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-11-04 09:01:40 +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