文章目录
- 前言
- 参考目录
- 功能实现的准备知识
- 1、目录结构说明
- 2、一些准备知识
- 2.1、自定义插件如何实现?
- 2.2、Mybatis 拦截器的拦截点?
- 2.3、关于 `@Intercepts` 注解?
- 2.4、关于拦截器中的 `Interceptor()` 方法和 `plugin()` 方法?
- 功能调用流程分析
- 1、说明
- 1.1、数据加密配置
- 1.2、加密实体类
- 1.3、Mapper(非必须)
- 1.4、测试方法
- 2、加密过程的实现分析
- 2.1、拦截加密实现方法
- 2.2、自定义加密处理器
- 2.2.1、获取类加密字段缓存
- 2.2.2、属性值替换
- 2.2.3、字段加密调用过程
- 2.3、字段加密完成
- 3、解密过程的实现分析
- 3.1、拦截解密实现方法
- 3.2、自定义解密处理器
- 3.2.1、获取类加密字段缓存
- 3.2.2、属性值替换
- 3.2.3、字段解密调用过程
- 3.3、字段解密完成
前言
前段时间,在群里大佬们讨论了关于数据存储加密的相关需求,后面就有了关于这个功能的 PR,在 框架 5.X 版本 中,这个功能被抽取成了独立的组件,所以本文来分析一下这个功能的实现。
值得一提的是,基于这个功能,并且借助最近大火的 ChatGPT
,对于 Mybatis 的自定义插件的实现过程,我有了更进一步的了解,并且最近也在结合书籍进行 Mybatis 源码的阅读,受益匪浅,有空的话会单独再对书中的笔记进行整理分享。
参考目录
- 数据加密
主要是关于该功能的使用说明。 - 通用源码阅读指导书:MyBatis源码详解
对于 Mybatis 源码(基于V3.5.2
版本,目前框架版本为V3.5.11
)学习的资料,写得很详细并且易于上手。
功能实现的准备知识
1、目录结构说明
类 | 说明 | 功能 |
---|---|---|
EncryptField | 字段加密注解 | 标注需要加密的字段,用于实体类字段上 |
AlgorithmType | 算法枚举 | 加密注解的加密算法 algorithm() |
EncodeType | 编码类型枚举 | 加密注解的编码类型 encode() |
EncryptorAutoConfiguration | 加解密模块配置类 | 配置初始化,注册加密解密拦截器以及加密管理类 |
EncryptorProperties | 加解密属性配置类 | Yaml 配置 |
EncryptorManager | 加密管理类 | 加解密功能的缓存管理以及方法调用 |
EncryptContext | 加密上下文对象 | 用于 encryptor 传递必要的参数 |
IEncryptor | 加解密接口 | 提供加解密接口用于自定义扩展 |
*Encryptor | 加解密现类 | 根据 AlgorithmType 以及 EncodeType 提供不同的加解密实现 |
MybatisEncryptInterceptor | 入参加密拦截器 | 加密功能核心实现类 |
MybatisDecryptInterceptor | 出参解密拦截器 | 解密功能核心实现类 |
2、一些准备知识
通过上面的目录结构,其实可以知道的是,要实现加解密功能,需要重点关注的是两个 Mybatis 拦截器。
关于这一部分,我看了源码以及书,也请教了一下 ChatGPT
,下面是整理后的一些内容,了解了之后可以对这个功能先有一个大致的了解。
2.1、自定义插件如何实现?
Mybatis的自定义插件是通过实现Mybatis提供的拦截器接口实现的。
这里的拦截器接口就是指 org.apache.ibatis.plugin.Interceptor
,框架中 MybatisEncryptInterceptor
以及 MybatisDecryptInterceptor
也分别实现了这个接口。
2.2、Mybatis 拦截器的拦截点?
加密操作:对 ParameterHandler
进行拦截处理,拦截参数设置。
解密操作:对 ResultSetHandler
进行拦截处理,拦截结果集处理过程。
2.3、关于 @Intercepts
注解?
2.4、关于拦截器中的 Interceptor()
方法和 plugin()
方法?
MybatisEncryptInterceptor#plugin
MybatisDecryptInterceptor#intercept
下面通过 Debug 结合框架中的代码来分析一下这个功能。
功能调用流程分析
1、说明
1.1、数据加密配置
application.yml
本文使用默认配置进行说明,其他配置可以参考框架 wiki。
1.2、加密实体类
com.ruoyi.demo.domain.TestDemoEncrypt
1.3、Mapper(非必须)
com.ruoyi.demo.mapper.TestDemoEncryptMapper
由于我使用的是开发中的分支,加入了多租户插件,这里为了避免报错所以使用了插件忽略注解。
1.4、测试方法
com.ruoyi.demo.controller.TestEncryptController
这是框架内置的测试方法,如果想要更好地了解执行过程,也可以对此方法拆分再进行 Debug 分析。如下:
加密解密操作可以理解成是一个相互的过程,有加密就有解密。理解了其中一个之后,另一个其实也是类似的过程。下面分别会对这两个过程进行分析。
2、加密过程的实现分析
2.1、拦截加密实现方法
MybatisEncryptInterceptor#plugin
2.2、自定义加密处理器
MybatisEncryptInterceptor#encryptHandler
2.2.1、获取类加密字段缓存
EncryptorManager#getFieldCache
由于第一次调用缓存中没有,所以对所有标注 @EncryptField
注解的字段设置属性获取权限,并返回结果集。
2.2.2、属性值替换
2.2.3、字段加密调用过程
MybatisEncryptInterceptor#encryptField
根据注解以及配置创建加解密上下文对象,调用加密方法。
EncryptorManager#encrypt
根据加密算法创建对应的加密器,并且存入缓存。
EncryptorManager#registAndGetEncryptor
调用加密器的加密方法进行加密。
Base64Encryptor#encrypt
2.3、字段加密完成
加密完成后,返回步骤 #2.2
进行属性值替换。
循环替换完成后的结果:
所有字段加密完成后,回到拦截方法并返回目标对象。
3、解密过程的实现分析
3.1、拦截解密实现方法
MybatisDecryptInterceptor#intercept
3.2、自定义解密处理器
MybatisDecryptInterceptor#decryptHandler
3.2.1、获取类加密字段缓存
这一步和加密类似,由于加密过程中已经将字段存入缓存中,因此可以直接获取到。验证方法:
3.2.2、属性值替换
上一步获取到类加密字段缓存后,循环进行字段解密。
3.2.3、字段解密调用过程
MybatisDecryptInterceptor#decryptField
根据注解以及配置创建加解密上下文对象,调用解密方法。
如果是第一次调用,同样会根据加密算法创建对应的加密器,并且存入缓存。
调用加密器的解密方法进行解密。
Base64Encryptor#decrypt
3.3、字段解密完成
解密完成后,返回步骤 #3.2
进行属性值替换。
循环替换完成后的结果:
所有字段解密完成后,回到拦截方法并返回执行结果。
以上是加解密调用流程的分析。
(完)