基于MyBatis插件实现字段加解密的实现示例

 更新时间:2025年11月18日 09:26:23   作者:Wh1te  
本文主要介绍了基于MyBatis插件实现字段加解密的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

对于大多数系统来说,敏感数据的加密存储都是必须考虑和实现的。最近在公司的项目中也接到了相关的安全需求,因为项目使用了 MyBatis 作为数据库持久层框架,在经过一番调研后决定使用其插件机制来实现字段加解密功能,并且封装成一个轻量级、支持配置、方便扩展的组件提供给其他项目使用。

MyBatis 的插件机制

简介

MyBatis 提供了插件功能,它允许你拦截 MyBatis 执行过程中的某个方法,对其增加自定义操作。默认情况下,MyBatis 允许拦截的方法包括:

方法说明
org.apache.ibatis.executor.Executorupdate, query, flushStatements, commit, rollback, getTransaction, close, isClosed拦截执行器的方法
org.apache.ibatis.executor.parameter.ParameterHandlergetParameterObject, setParameters说明:拦截参数处理的方法
org.apache.ibatis.executor.resultset.ResultSetHandlerhandleResultSets, handleOutputParameters拦截结果集处理的方法
org.apache.ibatis.executor.statement.StatementHandlerprepare, parameterize, batch, update, query拦截 Sql 语句构建的方法

插件实现

在 MyBatis 中,一个插件其实就是一个拦截器,插件的实现方式非常简单,只需要实现 org.apache.ibatis.plugin.Interceptor 接口,并且通过 @Intercepts 注解指定要拦截的方法签名即可。 以下是官方文档提供的例子:

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre-processing if needed
    Object returnObject = invocation.proceed();
    // implement post-processing if needed
    return returnObject;
  }

  @Override
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}

上面的插件会拦截 org.apache.ibatis.executor.Executor#update 方法的所有调用,你可以在 invocation.proceed() 前后增加插件逻辑。

字段加解密实现

对于字段加解密来说,需要关注的点就是查询、插入和更新,在进行这些操作的时候需要对字段进行处理(插入、更新时加密,查询时解密),与上文提到的拦截点的对应关系如下:

  • 插入、更新:org.apache.ibatis.executor.Executor#update
  • 查询:org.apache.ibatis.executor.resultset.ResultSetHandler#handleResultSets

代码实现

先上完整代码:github.com/WhiteDG/myb…

一般场景下只有包含敏感数据的字段才需要进行加解密,所以需要一个注解来标记哪些字段需要加解密,这里定义为 @EncryptedField,提供两个属性:

  • key:加解密时用到的密钥
  • encryptor:指定加解密器,不指定则使用全局的加解密器
@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptedField {

    String key() default "";

    Class<? extends IEncryptor> encryptor() default IEncryptor.class;
}

加密的整体思路就是通过 Invocation 拿到方法参数,有两种情况:一种是实体类,一种是 ParamMap,实体类通过注解确定需要加密的字段,ParamMap 通过配置的参数名前缀确定需要加密的字段,然后使用加密器对需要加密的字段进行加密覆盖掉原始值即可。如果是实体类则在方法执行完成后还需要对 key 字段进行回写处理。 在对参数进行处理前使用 Kryo 拷贝了一份源数据,目的是保留方法调用时的原始参数,避免经过插件的 SQL 执行完成后,原始参数变成已加密的。

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        if (Util.encryptionRequired(parameter, ms.getSqlCommandType())) {
            Kryo kryo = null;
            try {
                kryo = KryoPool.obtain();
                Object copiedParameter = kryo.copy(parameter);
                boolean isParamMap = parameter instanceof MapperMethod.ParamMap;
                if (isParamMap) {
                    //noinspection unchecked
                    MapperMethod.ParamMap<Object> paramMap = (MapperMethod.ParamMap<Object>) copiedParameter;
                    encryptParamMap(paramMap);
                } else {
                    encryptEntity(copiedParameter);
                }
                args[1] = copiedParameter;
                Object result = invocation.proceed();
                if (!isParamMap) {
                    handleKeyProperties(ms, parameter, copiedParameter);
                }
                return result;
            } finally {
                if (kryo != null) {
                    KryoPool.free(kryo);
                }
            }
        } else {
            return invocation.proceed();
        }
    }

解密插件与加密插件类似,通过 Invocation 拿到查询 SQL 执行后返回的结果集,有两种情况:一种是返回 ArrayList,一种是返回单个实体,同样通过注解确定需要解密的字段对其进行解密然后覆盖掉原始加密的值。

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        if (result == null) {
            return null;
        }
        if (result instanceof ArrayList) {
            //noinspection rawtypes
            ArrayList resultList = (ArrayList) result;
            if (resultList.isEmpty()) {
                return result;
            }
            Object firstItem = resultList.get(0);
            boolean needToDecrypt = Util.decryptionRequired(firstItem);
            if (!needToDecrypt) {
                return result;
            }
            Set<Field> encryptedFields = EncryptedFieldsProvider.get(firstItem.getClass());
            if (encryptedFields == null || encryptedFields.isEmpty()) {
                return result;
            }
            for (Object item : resultList) {
                decryptEntity(encryptedFields, item);
            }
        } else {
            if (Util.decryptionRequired(result)) {
                decryptEntity(EncryptedFieldsProvider.get(result.getClass()), result);
            }
        }
        return result;
    }

支持配置、方便扩展的实现

作为一个通用的组件(这里命名为 mybatis-crypto),支持配置和方便扩展是基本的要求。

mybatis-crypto 提供了以下几个配置项满足基本使用:

配置项说明默认值
mybatis-crypto.enabled是否启用 mybatis-cryptotrue
mybatis-crypto.fail-fast快速失败,加解密过程中发生异常是否中断。true:抛出异常,false:使用原始值,打印 warn 级别日志true
mybatis-crypto.mapped-key-prefixes@Param 参数名的前缀,前缀匹配则会进行加密处理
mybatis-crypto.default-encryptor全局默认 Encryptor
mybatis-crypto.default-key全局默认 Encryptor 的密钥

mybatis-crypto 核心包默认不提供具体的加解密方法,开发者可以通过引入 mybatis-crypto-encryptors 使用其提供的常用加解密类,或者实现 io.github.whitedg.mybatis.crypto.IEncryptor 自行扩展加解密方法。

Starter 封装

目前大多数项目都是基于 spring-boot 进行开发的,所以将 mybatis-crypto 封装成一个 starter 会更方便开发者使用。starter 的主要工作就是读取配置,自动装配,因此它的实现非常简单,只有两个类,MybatisCryptoProperties 获取配置,MyBatisCryptoAutoConfiguration 加载插件。

@Configuration
@ConditionalOnProperty(value = "mybatis-crypto.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(MybatisCryptoProperties.class)
public class MyBatisCryptoAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(MybatisEncryptionPlugin.class)
    public MybatisEncryptionPlugin encryptionInterceptor(MybatisCryptoProperties properties) {
        return new MybatisEncryptionPlugin(properties.toMybatisCryptoConfig());
    }

    @Bean
    @ConditionalOnMissingBean(MybatisDecryptionPlugin.class)
    public MybatisDecryptionPlugin decryptionInterceptor(MybatisCryptoProperties properties) {
        return new MybatisDecryptionPlugin(properties.toMybatisCryptoConfig());
    }

}

总结

本文简单介绍了基于 mybatis 插件机制实现字段加解密的思路及流程,并将其封装成一个通用的 spring-boot-starter 组件,开发者可以方便的引入使用,同时也提供了加解密方法集合 mybatis-crypto-encryptors

组件的具体使用方法和示https://github.com/WhiteDG/mybatis-crypto

到此这篇关于基于MyBatis插件实现字段加解密的实现示例的文章就介绍到这了,更多相关MyBatis 字段加解密内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Maven分模块开发与依赖管理和聚合和继承及属性深入详细介绍

    Maven分模块开发与依赖管理和聚合和继承及属性深入详细介绍

    依赖管理是项目管理中非常重要的一环。几乎任何项目开发的时候需要都需要使用到库。而这些库很可能又依赖别的库,这样整个项目的依赖形成了一个树状结构,而随着这个依赖的树的延伸和扩大,一系列问题就会随之产生
    2022-10-10
  • Java BigDecimal类的使用和注意事项

    Java BigDecimal类的使用和注意事项

    这篇文章主要讲解Java中BigDecimal类的用法,并简单介绍一些注意事项,希望能给大家做一个参考。
    2016-06-06
  • 简单谈谈ThreadPoolExecutor线程池之submit方法

    简单谈谈ThreadPoolExecutor线程池之submit方法

    下面小编就为大家带来一篇简单谈谈ThreadPoolExecutor线程池之submit方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • java并发编程之ThreadLocal详解

    java并发编程之ThreadLocal详解

    在锁的使用中会导致运行效率降低,ThreadLocal的使用彻底避免对共享资源的竞争,同时又可以不影响效率。本文详细讲解了ThreadLocal,需要了解的小伙伴可以看一看这篇文章
    2021-08-08
  • flowable动态创建多级流程模板实现demo

    flowable动态创建多级流程模板实现demo

    这篇文章主要为大家介绍了flowable动态创建多级流程模板实现demo,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • SpringBoot使用Jasypt对YML文件配置内容加密的方法(数据库密码加密)

    SpringBoot使用Jasypt对YML文件配置内容加密的方法(数据库密码加密)

    本文介绍了如何在SpringBoot项目中使用Jasypt对application.yml文件中的敏感信息(如数据库密码)进行加密,通过引入Jasypt依赖、配置加密密钥、加密敏感信息并测试解密功能,可以提高配置文件的安全性,减少因配置文件泄露导致的安全风险,感兴趣的朋友一起看看吧
    2025-03-03
  • Git工具 conflict冲突问题解决方案

    Git工具 conflict冲突问题解决方案

    这篇文章主要介绍了Git工具 conflict冲突问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • 带你快速搞定java并发库

    带你快速搞定java并发库

    本文主要介绍了java高并发写入用户信息到数据库的几种方法,具有很好的参考价值。下面跟着小编一起来看下吧,希望能给你带来帮助
    2021-07-07
  • Java的JDBC和桥接模式详解

    Java的JDBC和桥接模式详解

    下面小编就为大家带来一篇Java的JDBC和桥接模式(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2021-09-09
  • Spring Boot 如何使用Liquibase 进行数据库迁移(操作方法)

    Spring Boot 如何使用Liquibase 进行数据库迁移(操作方法)

    在Spring Boot应用程序中使用Liquibase进行数据库迁移是一种强大的方式来管理数据库模式的变化,本文重点讲解如何在Spring Boot应用程序中使用Liquibase进行数据库迁移,从而更好地管理数据库模式的变化,感兴趣的朋友跟随小编一起看看吧
    2023-09-09

最新评论