MyBatis参数加解密

一、概述

MyBatis中通过拦截器实现SQL参数加密/结果集解密是数据安全的常见场景,核心是拦截参数处理环节(加密入参)和结果集处理环节(解密出参)。

适配Spring Boot3 + MyBatis环境。

核心思路

拦截点 作用 拦截接口 拦截方法
参数加密 执行SQL前加密敏感参数 ParameterHandler setParameters(PreparedStatement)
结果集解密 查询后解密敏感字段 ResultSetHandler handleResultSets(Statement)

二、实现步骤

2.1 定义加密注解

通过自定义注解标记实体类中需要加密/解密的字段,让拦截器识别目标字段:

import java.lang.annotation.*;

/**
 * 敏感字段加解密注解
 */
@Target(ElementType.FIELD) // 作用于字段
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Documented
public @interface EncryptField {
    // 可扩展:指定加密算法类型,默认 AES
    String algorithm() default "AES";
}


import java.lang.annotation.*;

/**
 * 标记需要解密的字段
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
    // 可扩展:指定加密算法类型,默认AES
    String algorithm() default "AES";
}

2.2 加密工具类

封装通用的加密/解密方法(实际项目建议使用更安全的算法,如RSA+AES混合):

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;

/**
 * 加解密工具类(AES示例)
 */
public class EncryptUtils {
    // 密钥(生产环境需从配置中心/环境变量读取,避免硬编码)
    private static final String AES_KEY = "1234567890123456"; //AES密钥长度必须16/24/32位
    private static final String AES_ALGORITHM = "AES";

    /**
     * AES加密
     */
    public static String aesEncrypt(String content) {
        if (content == null || content.isEmpty()) {
            return content;
        }
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(AES_KEY.getBytes());
            keyGenerator.init(128, secureRandom);
            SecretKey secretKey = keyGenerator.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, AES_ALGORITHM);
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            byte[] encryptedBytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
            return Base64.encodeBase64String(encryptedBytes);
        } catch (Exception e) {
            throw new RuntimeException("AES加密失败", e);
        }
    }

    /**
     * AES解密
     */
    public static String aesDecrypt(String encryptedContent) {
        if (encryptedContent == null || encryptedContent.isEmpty()) {
            return encryptedContent;
        }
        try {
            byte[] encryptedBytes = Base64.decodeBase64(encryptedContent);
            KeyGenerator keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(AES_KEY.getBytes());
            keyGenerator.init(128, secureRandom);
            SecretKey secretKey = keyGenerator.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, AES_ALGORITHM);
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            return new String(decryptedBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("AES解密失败", e);
        }
    }
}

2.3 加密拦截器

拦截ParameterHandlersetParameters方法,对标记@EncryptField的字段加密:


import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Properties;

/**
 * 参数加密拦截器:拦截入参,对@EncryptField字段加密
 */
@Intercepts({
        @Signature(
                type = ParameterHandler.class,
                method = "setParameters",
                args = {PreparedStatement.class})
})
public class EncryptParameterInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 获取ParameterHandler
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        // 2. 通过反射获取参数对象
        MetaObject metaObject = SystemMetaObject.forObject(parameterHandler);
        Object parameterObject = metaObject.getValue("parameterObject");
        if (parameterObject == null) {
            return invocation.proceed();
        }
        // 3. 遍历参数对象的字段,对@EncryptField字段加密
        encryptField(parameterObject);
        // 4. 执行原方法
        return invocation.proceed();
    }

    /**
     * 加密对象中标记@EncryptField的字段
     */
    private void encryptField(Object obj) throws IllegalAccessException {
        if (obj == null) {
            return;
        }
        Class<?> clazz = obj.getClass();
        // 处理基本类型包装类/字符串(若参数是单个值)
        if (obj instanceof String) {
            return; // 单个字符串参数需特殊处理,此处示例针对实体类
        }
        // 遍历所有字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            // 检查是否有@EncryptField注解
            if (field.isAnnotationPresent(EncryptField.class)) {
                field.setAccessible(true);
                Object value = field.get(obj);
                if (value instanceof String) {
                    // 加密字段值
                    String encryptedValue = EncryptUtils.aesEncrypt((String) value);
                    field.set(obj, encryptedValue);
                }
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        // 仅对ParameterHandler类型创建代理
        if (target instanceof ParameterHandler) {
            // 生成代理对象
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
        // 可配置拦截器参数,此处暂不处理
    }
}

2.4 解密拦截器

拦截ResultSetHandlerhandleResultSets方法,对标记@DecryptField的字段解密:

import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;

/**
 * 结果集解密拦截器:拦截出参,对@DecryptField字段解密
 */
@Intercepts({
        @Signature(
                type = ResultSetHandler.class,
                method = "handleResultSets",
                args = {Statement.class}
        )
})
public class DecryptResultSetInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1. 执行原方法,获取结果集
        Object result = invocation.proceed();
        if (result == null) {
            return null;
        }
        // 2. 对结果集解密(支持单对象/列表)
        if (result instanceof List) {
            List<?> list = (List<?>) result;
            for (Object obj : list) {
                decryptField(obj);
            }
        } else {
            decryptField(result);
        }
        return result;
    }

    /**
     * 解密对象中标记@DecryptField的字段
     */
    private void decryptField(Object obj) throws IllegalAccessException {
        if (obj == null) {
            return;
        }
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(DecryptField.class)) {
                EncryptField annotation = field.getAnnotation(EncryptField.class);
                Object value = field.get(obj);
                if (value instanceof String) {
                    // 解密字段值
                    String decryptedValue = EncryptUtils.aesDecrypt((String) value);
                    field.set(obj, decryptedValue);
                }
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可配置拦截器参数
    }
}

2.5 注册拦截器

Spring Boot 3 配置拦截器

将拦截器注册到MyBatisSqlSessionFactory中:

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
@MapperScan("com.yourpackage.mapper") // 替换Mapper包路径
public class MyBatisConfig {

    /**
     * 注册参数加密拦截器
     */
    @Bean
    public EncryptParameterInterceptor encryptParameterInterceptor() {
        return new EncryptParameterInterceptor();
    }

    /**
     * 注册结果集解密拦截器
     */
    @Bean
    public DecryptResultSetInterceptor decryptResultSetInterceptor() {
        return new DecryptResultSetInterceptor();
    }

    /**
     * 配置SqlSessionFactory,添加拦截器
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(
            DataSource dataSource,
            EncryptParameterInterceptor encryptInterceptor,
            DecryptResultSetInterceptor decryptInterceptor
    ) throws Exception {
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);

        // 加载Mapper.xml(可选,注解版Mapper可省略)
        sessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml")
        );

        // 核心:注册拦截器
        sessionFactoryBean.setPlugins(encryptInterceptor, decryptInterceptor);

        // 可选:MyBatis全局配置
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true); // 下划线转驼峰
        sessionFactoryBean.setConfiguration(configuration);
        return sessionFactoryBean.getObject();
    }
}

2.6 实战使用

在实体类中标记需要加密/解密的字段:

public class User {

    private Long id;
    private String userName;

    // 加密存储:入参加密,数据库存密文
    @EncryptField
    @DecryptField // 查询时解密
    private String phone;

    // 加密存储:入参加密,数据库存密文
    @EncryptField
    @DecryptField
    private String idCard;

    // getter/setter 省略
}

三、关键注意事项

3.1 性能优化

  • 避免递归处理大量嵌套对象,可限制递归深度;
  • 拦截器仅对标记注解的字段处理,减少反射开销;
  • 加密算法选择:AES性能优于RSA,建议敏感字段用AES加密,密钥用RSA加密存储。

3.2 特殊场景处理

  • 批量操作:拦截器已兼容List类型结果集,批量插入/查询无需额外处理;
  • 参数为Map:需修改encryptField方法,支持遍历Mapkey-value(判断value是否为标记注解的实体);
  • 空值处理:加密工具类已兼容空值,避免空指针。

3.3 安全增强

  • 加密密钥不要硬编码,建议从application.yml读取(可结合Spring Cloud Config/阿里云KMS);
  • 生产环境禁用ECB模式,改用CBC/GCM模式(需添加初始化向量IV);
  • 对加密后的字段建立索引时,需用密文查询(拦截器会自动加密入参,无需手动处理)。

3.4 调试技巧

  • 在拦截器的intercept方法中加日志/断点,确认参数是否加密、结果集是否解密;
  • 开启MyBatis SQL日志,查看执行的SQL参数是否为密文。

四、扩展场景

4.1 多算法支持

在注解中指定加密算法(如@EncryptField(algorithm="AES"));

封装支持多种算法的加密/解密方法,统一入口,根据算法标识分发逻辑:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;

/**
 * 多算法加密工具类
 */
public class MultiAlgEncryptUtils {

    // 算法密钥配置(实际项目建议从配置中心读取,此处为示例)
    private static final Map<String, String> ALG_KEY_MAP = new HashMap<>();

    static {
        ALG_KEY_MAP.put("AES", "1234567890123456"); // AES-128密钥(16位)
        ALG_KEY_MAP.put("SM4", "8765432109876543"); // SM4密钥(16位)
        ALG_KEY_MAP.put("RSA", "MIGfMA0GCSqGSIb3DQEBBQUAA4GNADCBiQKBgQC..."); // RSA公钥(示例)
    }

    // 加密函数映射(算法标识 -> 加密逻辑)
    private static final Map<String, BiFunction<String, String, String>> ENCRYPT_FUNC = new HashMap<>();

    // 解密函数映射(算法标识 -> 解密逻辑)
    private static final Map<String, BiFunction<String, String, String>> DECRYPT_FUNC = new HashMap<>();

    static {
        // 注册AES算法
        ENCRYPT_FUNC.put("AES", MultiAlgEncryptUtils::aesEncrypt);
        DECRYPT_FUNC.put("AES", MultiAlgEncryptUtils::aesDecrypt);
        
        // 注册SM4算法
        ENCRYPT_FUNC.put("SM4", MultiAlgEncryptUtils::sm4Encrypt);
        DECRYPT_FUNC.put("SM4", MultiAlgEncryptUtils::sm4Decrypt);
        
        // 注册RSA算法(示例,实际需处理密钥对)
        ENCRYPT_FUNC.put("RSA", MultiAlgEncryptUtils::rsaEncrypt);
        DECRYPT_FUNC.put("RSA", MultiAlgEncryptUtils::rsaDecrypt);
    }

    /**
     * 统一加密入口
     * 
     * @param content 待加密内容
     * @param algorithm 加密算法
     * @return 加密后内容(Base64编码)
     */
    public static String encrypt(String content, String algorithm) {
        if (content == null || content.isEmpty()) return content;
        // 校验算法是否支持
        if (!ENCRYPT_FUNC.containsKey(algorithm)) {
            throw new UnsupportedOperationException("不支持的加密算法:" + algorithm);
        }
        // 获取对应算法密钥(可通过config参数扩展密钥选择)
        String key = ALG_KEY_MAP.get(algorithm);
        // 执行加密
        return ENCRYPT_FUNC.get(algorithm).apply(content, key);
    }

    /**
     * 统一解密入口
     * 
     * @param content 待解密内容(Base64编码)
     * @param algorithm 解密算法
     * @return 解密后内容
     */
    public static String decrypt(String content, String algorithm) {
        if (content == null || content.isEmpty()) return content;
        if (!DECRYPT_FUNC.containsKey(algorithm)) {
            throw new UnsupportedOperationException("不支持的解密算法:" + algorithm);
        }
        String key = ALG_KEY_MAP.get(algorithm);
        return DECRYPT_FUNC.get(algorithm).apply(content, key);
    }

    // ------------------------------ 各算法具体实现 ------------------------------
    /**
     * AES加密(ECB模式,PKCS5Padding填充)
     */
    private static String aesEncrypt(String content, String key) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            throw new RuntimeException("AES加密失败", e);
        }
    }

    /**
     * AES解密
     */
    private static String aesDecrypt(String content, String key) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(content));
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("AES解密失败", e);
        }
    }

    /**
     * SM4加密(ECB模式,PKCS5Padding填充)
     */
    private static String sm4Encrypt(String content, String key) {
        try {
            // 实际项目需引入SM4算法依赖(如BouncyCastle)
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "SM4");
            Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            throw new RuntimeException("SM4加密失败", e);
        }
    }

    /**
     * SM4解密
     */
    private static String sm4Decrypt(String content, String key) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "SM4");
            Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(content));
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("SM4解密失败", e);
        }
    }

    /**
     * RSA加密(示例,实际需使用公钥加密,私钥解密)
     */
    private static String rsaEncrypt(String content, String publicKey) {
        // 实际实现需解析公钥、处理分块加密(RSA加密长度有限制)
        try {
            // 简化示例:直接返回Base64编码的内容(实际需替换为真实RSA加密逻辑)
            return Base64.getEncoder().encodeToString((content + "_RSA_ENC").getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            throw new RuntimeException("RSA加密失败", e);
        }
    }

    /**
     * RSA解密
     */
    private static String rsaDecrypt(String content, String privateKey) {
        try {
            // 简化示例:直接解码并去除加密标识
            String decrypted = new String(Base64.getDecoder().decode(content), StandardCharsets.UTF_8);
            return decrypted.replace("_RSA_ENC", "");
        } catch (Exception e) {
            throw new RuntimeException("RSA解密失败", e);
        }
    }
}

改造参数加密拦截器

 /**
  * 递归加密字段,根据注解指定的算法选择加密方式
  */
private void encryptField(Object obj) throws IllegalAccessException {
    if (obj == null) return;
    Class<?> clazz = obj.getClass();
    // 跳过基础类型和字符串
    if (clazz.isPrimitive() || clazz == String.class || obj instanceof Number) {
        return;
    }

    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        field.setAccessible(true);
        // 仅处理标记@EncryptField的字段
        if (field.isAnnotationPresent(EncryptField.class)) {
            EncryptField annotation = field.getAnnotation(EncryptField.class);
            String algorithm = annotation.algorithm(); // 获取注解指定的算法
            Object value = field.get(obj);
            if (value != null && value instanceof String) {
                // 调用多算法工具类加密
                String encryptedValue = MultiAlgEncryptUtils.encrypt((String) value, algorithm);
                field.set(obj, encryptedValue);
            }
        }
        // 递归处理嵌套对象
        encryptField(field.get(obj));
    }
}

4.2 拦截Map参数

@Target(value = {ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) // 作用于字段
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Documented
public @interface EncryptField {
    // 可扩展:指定加密算法类型,默认 AES
    String algorithm() default "AES";

    // 需要加密的Map字段名数组
    String[] fields() default "";
}
package com.example.demo.test3;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.Properties;

/**
 * 参数加密拦截器:拦截入参,对@EncryptField字段加密
 */
@Intercepts({
        @Signature(
                type = ParameterHandler.class,
                method = "setParameters",
                args = {PreparedStatement.class})
})
public class EncryptParameterInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(parameterHandler);

        // 1. 获取核心参数:MappedStatement(包含Mapper方法信息)、参数对象
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("mappedStatement");
        Object parameterObject = metaObject.getValue("parameterObject");

        if (parameterObject == null) {
            return invocation.proceed();
        }

        // 处理「单个字符串参数」(核心新增逻辑)
        if (parameterObject instanceof String) {
            encryptStringField(parameterObject, mappedStatement, metaObject);
        }
        // 处理「Map类型参数」
        else if (parameterObject instanceof Map) {
            encryptMapField((Map<Object, Object>) parameterObject, mappedStatement);
        } else {
            // 处理「实体类字段加密」
            encryptEntityField(parameterObject);
        }
        return invocation.proceed();
    }

    /**
     * 获取Mapper方法(从MappedStatement中解析)
     */
    private Method getMapperMethod(MappedStatement mappedStatement) throws ClassNotFoundException {
        String mappedStatementId = mappedStatement.getId();
        // 拆分:com.yourpackage.mapper.UserMapper.selectByPhone -> 类名 + 方法名
        String className = mappedStatementId.substring(0, mappedStatementId.lastIndexOf("."));
        String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);

        Class<?> mapperClass = Class.forName(className);
        // 遍历方法,匹配方法名(简化:假设无重载;有重载需匹配参数类型)
        for (Method method : mapperClass.getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
                return method;
            }
        }
        throw new RuntimeException("未找到Mapper方法:" + mappedStatementId);
    }

    /**
     * 检查Mapper方法的参数是否标记了@EncryptField
     */
    private EncryptField getEncryptParamAnnotation(Method mapperMethod) {
        Parameter[] parameters = mapperMethod.getParameters();
        for (Parameter param : parameters) {
            if (param.isAnnotationPresent(EncryptField.class)) {
                return param.getAnnotation(EncryptField.class);
            }
        }
        return null;
    }

    private void encryptStringField(Object parameterObject, MappedStatement mappedStatement,
                                    MetaObject metaObject) throws Throwable {
        // 获取Mapper方法的参数注解(判断是否需要加密)
        Method mapperMethod = getMapperMethod(mappedStatement);
        EncryptField encryptField = getEncryptParamAnnotation(mapperMethod);

        if (encryptField != null) {
            // 执行加密:根据注解指定的算法
            String algorithm = encryptField.algorithm();
            String encryptedValue = MultiAlgEncryptUtils.encrypt((String) parameterObject, algorithm);
            // 替换参数值为加密后的密文
            metaObject.setValue("parameterObject", encryptedValue);
        }
    }

    /**
     * 加密Map中的指定字段
     * 规则:1. 优先从Mapper方法参数注解获取需要加密的字段和算法;
     *      2. 兜底按字段名约定(如encrypt_前缀)
     */
    private void encryptMapField(Map<Object, Object> map, MappedStatement mappedStatement)
                                throws Throwable {
        if (map.isEmpty()) {
            return;
        }
        // 关键修复:将不可变的 Map<?,?> 转为可操作的 Map<Object, Object>
        // MyBatis中传入的Map本质都是<Object, Object>类型,此转换安全
        // 步骤1:从Mapper方法注解获取需要加密的Map字段配置(推荐方式)
        Method mapperMethod = getMapperMethod(mappedStatement);
        EncryptField encryptMapParam = mapperMethod.getAnnotation(EncryptField.class);
        if (encryptMapParam != null) {
            // 获取注解中指定的加密字段和算法
            String[] encryptFields = encryptMapParam.fields();
            String algorithm = encryptMapParam.algorithm();
            for (String field : encryptFields) {
                Object value = map.get(field);
                if (value instanceof String) {
                    String encryptedValue = MultiAlgEncryptUtils.encrypt((String) value, algorithm);
                    map.put(field, encryptedValue); // 用转换后的map执行put,解决泛型问题
                }
            }
            return;
        }

        // 步骤2:兜底规则(无注解时):加密以encrypt_为前缀的字段,默认AES算法
        for (Map.Entry<?, ?> entry : map.entrySet()) {
            Object key = entry.getKey();
            Object value = entry.getValue();
            if (key instanceof String && value instanceof String) {
                String fieldName = (String) key;
                if (fieldName.startsWith("encrypt_")) {
                    String encryptedValue = MultiAlgEncryptUtils.encrypt((String) value, "AES");
                    map.put(key, encryptedValue); // 用转换后的map执行put,解决泛型问题
                }
            }
        }
    }

    /**
     * 加密对象中标记@EncryptField的字段
     */
    private void encryptEntityField(Object obj) throws IllegalAccessException {
        if (obj == null) return;
        Class<?> clazz = obj.getClass();
        // 跳过基础类型和字符串(已单独处理)
        if (clazz.isPrimitive() || clazz == String.class || obj instanceof Number) {
            return;
        }
        // 遍历实体类字段,加密标记@EncryptField的字段
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(EncryptField.class)) {
                field.setAccessible(true);
                Object value = field.get(obj);
                if (value instanceof String) {
                    EncryptField annotation = field.getAnnotation(EncryptField.class);
                    String encryptedValue = MultiAlgEncryptUtils.encrypt((String) value, annotation.algorithm());
                    field.set(obj, encryptedValue);
                }
                // 递归处理嵌套实体
                encryptEntityField(field.get(obj));
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        // 仅对ParameterHandler类型创建代理
        if (target instanceof ParameterHandler) {
            // 生成代理对象
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
        // 可配置拦截器参数,此处暂不处理
    }
}

Map参数:

@Mapper
public interface UserMapper {
    @Insert("INSERT INTO user (username, phone, id_card) VALUES (#{userName}, #{phone}, #{idCard})")
    int insert(User user);

    @Select("SELECT id, username, phone, id_card as idCard FROM user WHERE id = #{id}")
    User selectById(Long id);

    @Select("SELECT id, username, phone, id_card as idCard FROM user WHERE id_card = #{idCard}")
    List<User> selectByIdCard(@EncryptField String idCard);

    @Insert("INSERT INTO user (username, phone, id_card) VALUES (#{userName}, #{phone}, #{idCard})")
    @EncryptField(fields = {"idCard", "phone"})
    int insertMap(Map<String, String> user);
}

4.3 其他

  • 动态加密字段:通过配置文件指定需要加密的字段,避免硬编码注解;
  • 脱敏+加密:解密后对敏感字段脱敏(如手机号显示为138****1234)。

通过以上实现,即可在MyBatis中全自动完成SQL参数加密和结果集解密,无需在业务代码中手动处理加密逻辑,保证数据安全的同时降低耦合。

posted @ 2025-12-08 21:57  夏尔_717  阅读(11)  评论(0)    收藏  举报