开发者

Mybatis敏感数据加解密插件实现过程

开发者 https://www.devze.com 2026-01-06 10:30 出处:网络 作者: dazhong2012
价值2999元 Java视频教程限时免费下载
专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
立即下载
目录1. MyBATis插件开发原理1.1 插件的作用与拦截机制1.2 插件的运行机制与责任链模式2. MyBatis + 注解实现加解密插件2.1 整体架构设计2.2 注解定义2.3 加密解密方法或函数工具类2.4 基于拦截器的加解密实现2.4.1 参
目录
  • 1. MyBATis插件开发原理
    • 1.1 插件的作用与拦截机制
    • 1.2 插件的运行机制与责任链模式
  • 2. MyBatis + 注解实现加解密插件
    • 2.1 整体架构设计
    • 2.2 注解定义
    • 2.3 加密解密方法或函数工具类
    • 2.4 基于拦截器的加解密实现
      • 2.4.1 参数加密拦截器
      • 2.4.2 结果集解密拦截器
    • 2.5 实体类使用
    • 3. 数据库层面加解密函数
      • 3.1 mysql加解密函数
        • 3.2 PostgreSQL加解密函数
          • 3.3 支持 mysql和 pgsql 的通用函数定义
            • 1. MySQL 加解密示例
            • 2. PostgreSQL 兼容 MySQL 的加解密示例
        • 四、相关开源项目 mybatis-encrypt-plugin 介绍
          • 1. 实现原理
            • 2. 使用方式
            • 总结

              本文将详细介绍基于MyBatis的敏感数据全链路安全处理方案,涵盖插件开发原理、具体实现代码以及数据库函数集成。

              1. MyBatis插件开发原理

              1.1 插件的作用与拦截机制

              MyBatis 插件是基于拦截器模式实现的,允许开发者在 MyBatis 执行 SQL语句的关键节点插入自定义逻辑。MyBatis 支持对四大核心组件进行拦截:

              • Executor:执行器,用于拦截增删改查操作
              • ParameterHandler:参数处理器,用于拦截参数设置
              • ResultSetHandler:结果集处理器,用于拦截结果集处理
              • StatementHandler:语句处理器,用于拦截SQL语句构建

                插件通过实现 Interceptor 接口并配合 @Intercepts@Signature注解来定义拦截规则。

              1.2 插件的运行机制与责任链模式

              MyBatis的插件体系采用责任链模式,通过InterceptorChain管理所有拦截器。当执行目标方法时,会依次调用所有注册的拦截器。具体实现机制如下:

              // 插件动态代理示例
              public Object plugin(Object target) {
                  if (target instanceof Executor) {
                      return Plugin.wrap(target, this);
                  }
                  return target;
              }
              

              动态代理机制确保在目标方法执行前后可以插入自定义逻辑,这是实现数据加解密的理论基础。

              2. MyBatis + 注解实现加解密插件

              2.1 整体架构设计

              • 本方案采用分层处理策略,将加解密与脱敏分离:
              • 数据访问层:通过MyBatis插件实现自动加解密
              • API展示层:通过Jackson序列化器实现数据脱敏

              2.2 注解定义

              首先定义用于标记敏感数据的注解:

              // 类级注解,标记需要加解密的实体
              @Inherited
              @Target({ElementType.TYPE})
              @Retention(RetentionPolicy.RUNTIME)
              public @interface SensitiveData {
              }
              
              // 加解密字段注解
              @Inherited
              @Target({ElementType.FIELD})
              @Retention(RetentionPolicy.RUNTIME)
              public @interface SensitiveField {
              }
              

              2.3 加密解密方法或函数工具类

              以 pgsql 中的加解密函数作为举例,实现加密解密方法调用:

              @Component
              public class PgCryptoUtil {
              
                  @Autowired
                  private JdbcTemplate jdbcTemplate;
              
                  /**
                   * 加密方法 - 返回字符串格式(如十六进制)
                   */
                  public String encrypt(String plainText, String key) throws Exception {
                      String sql = "SELECT encode(pgp_sym_encrypt(?, ?), 'hex')";
                      retujsrn jdbcTemplate.queryForObject(sql, String.class, plainText, key);
                  }
              
                  /**
                   * 解密方法 - 接收字符串格式的加密数据
                   */
                  public String decrypt(String encryptedText, String key) throws Exception {
                      // 假设encryptedText是十六进制格式的字符串
                      String sql = "SELECT pgp_sym_decrypt(decode(?, 'hex'), ?)";
                      return jdbcTemplate.queryForObject(sql, String.class, encryptedText, key);
                  }
              }
              

              2.4 基于拦截器的加解密实现

              2.4.1 参数加密拦截器

              拦截ParameterHandler.setParameters方法,在数据入库前进行加密。

              // 加密拦截器 - 用于处理INSERT/UPDATE操作
              @Component
              @Intercepts({
                      @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
              })
              public class EncryptInterceptor implements Interceptor {
              
                  @Autowired
                  private PgCryptoUtil pgCryptoUtil;
              
                  private final String ENCRYPTION_KEY = "1234567890abcdef";
              
                  @Override
                  public Object intercept(Invocation invocation) throws Throwable {
                      Object[] args = invocation.getArgs();
                      Object parameter = args[1];
              
                      // 加密处理
                      if (parameter != null) {
                          encryptParameters(parameter);
                      }
              
                      return invocation.proceed();
                  }
              
                  private void encryptParameters(Object parameter) {
                      if (parameter instanceof List) {
                          for (Object item : (List) parameter) {
                              encryptItem(item);
                          }
                      } else {
                          encryptItem(parameter);
                      }
                  }
              
                  private void encryptItem(Object item) {
                      if (item != null && item.getClass().isAnnotationPresent(SensitiveData.class)) {
                          MetaObject metaObject = SystemMetaObject.forObject(item);
                          Field[] fields = item.getClass().getDeclaredFields();
              
                          for (Field field : fields) {
                              if (field.isAnnotationPresent(SensitiveField.class) && field.getType() == String.class) {
                                  String fieldName = field.getName();
                                  Object value = metaObject.getValue(fieldName);
              
                                  if (value instanceof String 编程&& !((String) value).isEmpty()) {
                                      try {
                                          // 加密字符串数据
                                          String encryptedValue = pgCryptoUtil.encrypt((String) value, ENCRYPTION_KEY);
                                          metaObject.setValue(fieldName, encryptedValue);
                                      } catch (Exception e) {
                                          throw new RuntimeException("加密字段 " + fieldName + " 时发生错误", e);
                                      }
                                  }
                              }
                          }
                      }
                  }
              
                  @Override
                  public Object plugin(Object target) {
                      return Plugin.wrap(target, this);
                  }
              
                  @Override
                  public void setProperties(Properties properties) {
                  }
              }
              

              2.4.2 结果集解密拦截器

              拦截ResultSetHandler.handleResultSets方法,在数据查询后进行解密

              // 解密拦截器 - 用于处理SELECT操作(您的代码优化版)
              @Component
              @Intercepts({
                      @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
              })
              public class DecryptInterceptor implements Interceptor {
              
                  @Autowired
                  private PgCryptoUtil pgCryptoUtil;
              
                  private final String ENCRYPTION_KEY = "1234567890abcdef";
              
                  @Override
                  public Object intercept(Invocation invocation) throws Throwable {
                      Object result = invocation.proceed();
              
                      if (result == null) {
                          return null;
                      }
              
                      if (result instanceof List) {
                          for (Object item : (List) result) {
                              decryptItem(item);
                          }
                      } else {
                          decryptItem(result);
                      }
              
                      return result;
                  }
              
                  private void decryptItem(Object item) {
                      if (item != null && item.getClass().isAnnotationPresent(SensitiveData.class)) {
                          MetaObject metaObject = SystemMetaObject.forObject(item);
                          Field[] fields = item.getClass().getDeclaredFields();
              
                          for (Field field : fields) {
                              if (field.isAnnotationPresent(SensitiveField.class) && field.getType() == String.class) {
                                  String fieldName = field.getName();
                                  Object value = metaObje编程客栈ct.getValue(fieldName);
              
                                  // 关键修改:处理String类型的加密数据
                                  if (value instanceof String && !((String) value).isEmpty()) {
                                      try {
                                          String decryptedValue = pgCryptoUtil.decrypt((String) value, ENCRYPTION_KEY);
                                          metaObject.setValue(fieldName, decryptedValue);
                                      } catch (Exception e) {
                                          // 记录解密失败但不要抛出异常,以免影响正常查询
                                          System.err.println("解密字段 " + fieldName + " 失败: " + e.getMessage());
                                          // 可以选择保留原始加密值或设置为null
                                          // metaObject.setValue(fieldName, null);
                                      }
                                  }
                              }
                          }
                      }
                  }
              
                  @Override
                  public Object plugin(Object target) {
                      return Plugin.wrap(target, this);
                  }
              
                  @Override
                  public void setProperties(Properties properties) {
                  }
              }
              
              

              2.5 实体类使用

              @SensitiveData 
              @Data // lombok注解
              public class UserInfo extends BaseEntity implements Serializable {
              
                  /**
                   * 主键
                   */
                  private String id;
              
                  /**
                   *手机号
                   */
                  @SensitiveField
                  @Excel(name = "手机号")
                  private String phone;
              }
              

              3. 数据库层面加解密函数

              3.1 MySQL加解密函数

              MySQL原生支持AES加解密函数,可在SQL层面实现加解密:

              -- 加密数据插入
              INSERT INTO users (username, phone_encrypted) 
              VALUES ('john_doe', AES_ENCRYPT('13812345678', 'encryption_key'));
              
              -- 查询解密数据
              SELECT username, AES_DECRYPT(phone_encrypted, 'encryption_key') as phone 
              FROM users WHERE username = 'john_doe';
              
              

              3.2 PostgreSQL加解密函数

              PostgreSQL通过pgcrypto扩展提供加密功能:

              -- 启用pgcrypto扩展
              CREATE EXTENSION IF NOT EXISTS pgcrypto;
              
              -- 加密数据插入
              INSERT INTO users (username, phone_encrypted) 
              VALUES ('john_doe', pgp_sym_encrypt('13812345678', 'encryption_key'));
              
              -- 查询解密数据
              SELECT username, pgp_sym_decrypt(phone_encrypted, 'encryption_key') as phone 
              FROM users WHERE username = 'john_doe';
              
              -- 使用自定义函数简化操作
              CREATE FUNCTION encrypt_value(value TEXT, key TEXT) 
              RETURNS TEXT AS $$
              BEGIN
                  RETURN encode(pgp_sym_encrypt(value, key), 'base64');
              END;
              $$ LANGUAGE plpgsql;
              

              单纯在 PostgreSQL 中使用更安全的加密方式,推荐使用其默认的 PGP 函数。注意:此方法与 MySQL 默认加密不兼容。

              -- 加密 (使用默认更安全的CBC模式等选项)
              SELECT armor(pgp_sym_encrypt('hello', 'mykey')) AS encrypted_data_pgp;
              -- 解密
              SELECT pgp_sym_decrypt(dearmor('<上述armor函数输出的结果>'), 'mykey') AS decrypted_data_pgp;
              

              3.3 支持 mysql和 pgsql 的通用函数定义

              由于两种数据库默认的加密模式不同,为了实现兼容,核心思路是在 PostgreSQL 端使用 encrypt和 decrypt函数,并明确指定加密参数,以模拟 MySQL 的行为。

              以下示例均使用明文 hello和密钥 mykey。

              1. MySQL 加解密示例

              MySQL 使用默认的 AES-128-ECB 模式。

              -- 加密 (结果为二进制, 通常用 HEX 转换为十六进制字符串存储或查看)
              SELECT HEX(AES_ENCRYPT('hello', 'mykey')) AS encrypted_data;
              -- 结果示例: '744661B08EB4698B64DE167B7B43BCD7'
              
              -- 解密 (需先将十六进制字符串转换回二进制)
              SELECT AES_DECRYPT(UNHEX('744661B08EB4698B64DE167B7B43BCD7'), 'mykey') AS decrypted_data;
              -- 结果: 'hello'
              

              2. PostgreSQL 兼容 MySQL 的加解密示例

              在 PostgreSQL 中,使用 pgcrypto扩展的 encrypt/decrypt函数,并指定算法和模式为 aes-ecb以达到兼容。

              -- 首先确保启用 pgcrypto 扩展
              CREATE EXTENSION IF NOT EXISTS pgcrypto;
              
              -- 加密 (使用 'aes-ecb' 模式模拟MySQL,结果转换为十六进制以便比较和存储)
              SELECT encode(encrypt('hello', 'mykey', 'aes-ecb'), 'hex') AS encrypted_data;
              -- 结果应与MySQL一致: '744661B08EB4698B64DE167B7B43BCD7'
              
              -- 解密
              SELECT convert_from(decrypt(decode('744661B08EB4698B64DE167B7B43BCD7', 'hex'), 'mykey', 'aes-ecb'), 'utf8') AS decrypted_data;
              -- 结果: 'hello'
              

              四、相关开源项目 mybatis-encrypt-plugin 介绍

              mybatis-encrypt-plugin 是一款开源的基于mybatis的插件机制编写的一套敏感数据加解密以及数据脱敏工具。

              开源地址:https://github.com/andydazhong/mybatis-encrypt-plugin

              1. 实现原理

              • 拦截mybatis的StatementHandler 对读写请求进行脱敏和字段的加密。
              • 拦截mybatis的ResultSetHandler,对读请求的响应进行加密字段的解密赋值。

              2. 使用方式

              1. 导入依赖

               <!-- mybatis数据脱敏插件 -->
                  <dependency>
                      <groupId>com.github.chenhaiyangs</groupId>
                      <artifactId>mybatis-encrypt-plugin</artifactId>
                      <version>1.0.0</version>
                  </dependency>
              

              2. 编写加解密实现类以及配置mybatis的插件

              下面在springboot场景下的一个配置案例。

               /**
                   * 插件配置
                   */
                  @Configuration
                  public class EncryptPluginConfig {
                  
                      //加密方式
                      @Bean
                      Encrypt encryptor() throws Exception{
                          return new AesSupport("1870577f29b17d6787782f35998c4a79");
                      }
                      
                      //配置插件
                      @Bean
                      ConfigurationCustomizer configurationCustomizer() throws Exception{
                          DecryptReadInterceptor decryptReadInterceptor = new DecryptReadIntercepthttp://www.devze.comor(encryptor());
                          SensitiveAndEncryptWriteInterceptor sensitiveAndEncryptWriteInterceptor = new SensitiveAndEncryptWriteInterceptor(encryptor());
                  
                          return (configuration) -> {
                              configuration.addInterceptor(decryptReadInterceptor);
                              configuration.addInterceptor(sensitiveAndEncryptWriteInterceptor);
                          };
                      }
                  }
              

              3. 在vo类上添加功能注解使得插件生效

              @SensitiveEncryptEnabled
              @Data
              public class UserDTO {
              
                 private Integer id;
                  /**
                   * 用户名
                   */
                  @EncryptField
                  private String userName;
                  /**
                   * 脱敏的用户名
                   */
                  @SensitiveField(SensitiveType.CHINESE_NAME)
                  private String userNameSensitive;
                  /**
                   * 值的赋值不从数据库取,而是从userName字段获得。
                   */
                  @SensitiveBinded(bindField = "userName",value = SensitiveType.CHINESE_NAME)
                  private String userNameOnlyDTO;
                  /**
                   * 身份证号
                   */
                  @EncryptField
                  private String idcard;
                  /**
                   * 脱敏的身份证号
                   */
                  @SensitiveField(SensitiveType.ID_CARD)
                  private String idcardSensitive;
                  /**
                   * 一个json串,需要脱敏
                   * SensitiveJSONField标记json中需要脱敏的字段
                   */
                  @SensitiveJSONField(sensitivelist = {
                          @SensitiveJSONFieldKey(key = "idcard",type = SensitiveType.ID_CARD),
                          @SensitiveJSONFiehttp://www.devze.comldKey(key = "username",type = SensitiveType.CHINESE_NAME),
                  })
                  private String jsonStr;
              
                  private int age;
              
                  @SensitiveField(SensitiveType.EMAIL)
                  private String email;
              }
              

              总结

              以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

              0
              价值2999元 Java视频教程限时免费下载
              专为Java开发者设计,涵盖核心技术、架构设计、性能优化等
              立即下载

              精彩评论

              暂无评论...
              验证码 换一张
              取 消