Mybatis数据加密解密

文章目录

      • Mybatis数据加密解密
          • 一、自定义注解
          • 二、自定义参数处理拦截器
          • 结果集拦截器
          • 加密解密

Mybatis数据加密解密

方案一:Mybatis拦截器之数据加密解密【Interceptor】
拦截器介绍
Mybatis Interceptor 在 Mybatis 中被当作 Plugin(插件),不知道为什么,但确实是在 org.apache.ibatis.plugin 包下面

既然是拦截器,可以拦截哪些内容呢?试想一下… 当程序写到持久层时,Mybatis 会 执行 指定 SQL 语句,并处理 请求参数 和 返回值。没错,Mybatis 拦截器可以帮助我们处理上述内容,请看官网的 Plugins 的片段, 内容不多

// 执行
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
// 请求参数处理
ParameterHandler (getParameterObject, setParameters)
// 返回结果集处理
ResultSetHandler (handleResultSets, handleOutputParameters)
// SQL语句构建
StatementHandler (prepare, parameterize, batch, update, query)

拦截器的使用
如果需要实现自定义的拦截器,只需要实现 org.apache.ibatis.plugin.Interceptor 接口,该接口有三个方法:

Object intercept(Invocation invocation) throws Throwable;Object plugin(Object target);void setProperties(Properties properties);

我们要实现数据加密,进入数据库的字段不能是真实的数据,但是返回来的数据要真实可用,所以我们需要针对 Parameter 和 ResultSet 两种类型处理,同时为了更灵活的使用,我们需要自定义注解

一、自定义注解

类注解,将注解放在实体类上

/*** 需要加解密的类注解*/
@Documented
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptClass {
}

字段注解,将注解放在实体字段上

/*** 加密字段注解*/
@Documented
@Inherited
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {}

有了这两个注解,我们可以在我们可以标记我们要处理的实体和实体中的字段

二、自定义参数处理拦截器

参考官网,通过 @Intercepts 和 @Signature 的联合使用,指定 ParameterHandler.class 类型,同时通过 @Component 注解注入到容器中,即可在设置参数的时候进行拦截,通过自定义接口 IEncryptDecrypt, 根据 Field 的各种类型自定义加密解密算法

@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
@ConditionalOnProperty(value = "domain.encrypt", havingValue = "true")
@Component
@Slf4j
public class ParammeterInterceptor  implements Interceptor {@Autowiredprivate IEncryptDecrypt encryptDecrypt;@Overridepublic Object intercept(Invocation invocation) throws Throwable {log.info("拦截器ParamInterceptor");//拦截 ParameterHandler 的 setParameters 方法 动态设置参数if (invocation.getTarget() instanceof ParameterHandler) {ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];// 反射获取 BoundSql 对象,此对象包含生成的sql和sql的参数map映射/*Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");boundSqlField.setAccessible(true);BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);*/// 反射获取 参数对像Field parameterField =parameterHandler.getClass().getDeclaredField("parameterObject");parameterField.setAccessible(true);Object parameterObject = parameterField.get(parameterHandler);if (Objects.nonNull(parameterObject)){Class<?> parameterObjectClass = parameterObject.getClass();EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);if (Objects.nonNull(encryptDecryptClass)){Field[] declaredFields = parameterObjectClass.getDeclaredFields();final Object encrypt = encryptDecrypt.encrypt(declaredFields, parameterObject);}}}return invocation.proceed();}@Overridepublic Object plugin(Object o) {return Plugin.wrap(o, this);}@Overridepublic void setProperties(Properties properties) {}
}

同样新建结果集拦截器

结果集拦截器

与参数拦截器基本一样, 只不过类型指定为 ResultSetHandler.class

@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args={Statement.class})
})
@ConditionalOnProperty(value = "domain.decrypt", havingValue = "true")
@Component
@Slf4j
public class ResultInterceptor implements Interceptor {@Autowiredprivate IEncryptDecrypt encryptDecrypt;@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object result = invocation.proceed();if (Objects.isNull(result)){return null;}if (result instanceof ArrayList) {ArrayList resultList = (ArrayList) result;if (CollectionUtils.isNotEmpty(resultList) && needToDecrypt(resultList.get(0))){for (int i = 0; i < resultList.size(); i++) {encryptDecrypt.decrypt(resultList.get(i));}}}else {if (needToDecrypt(result)){encryptDecrypt.decrypt(result);}}return result;}public boolean needToDecrypt(Object object){Class<?> objectClass = object.getClass();EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(objectClass, EncryptDecryptClass.class);if (Objects.nonNull(encryptDecryptClass)){return true;}return false;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
加密解密

IEncryptDecrypt 接口定义了 加密和解密两个方法,

public interface IEncryptDecrypt {/*** 加密方法* @param declaredFields 反射bean成员变量* @param parameterObject Mybatis入参* @param <T>* @return*/public <T> T encrypt(Field[] declaredFields, T parameterObject) throws IllegalAccessException;/*** 解密方法* @param result Mybatis 返回值,需要判断是否是ArrayList类型* @param <T>* @return*/public <T> T decrypt(T result) throws IllegalAccessException;
}

两个拦截器通过在 YAML 中配置属性,按条件注入,外加自定义加密解密算法,完成全局灵活的配置。
核心代码已上传至 Github Demo

方案二:Mybatis利用内置类型转换器【typeHandler】

mybatis 利用内置类型转换器(「typeHandler」),实现 Java 类型与 JDBC 类型的相互转换,我们正好可以利用这个特性,在转换之前加入加解密步骤。

typeHandler 底层原理不是复杂,如果我们没有使用 Mybatis,而是直接使用最原始的 JDBC 执行查询语句,相关代码如下:

我们需要手动判断 Java 类型,然后调用 PreparedStatement设置合适类型参数。获取返回结果之后,又需要手动调用 ResultSet 结果集获取相应类型的数据,这个过程十分繁琐。使用 mybatis 之后,上述步骤就无需我们再实现了。mybatis 可以通过识别 Java/JDBC 类型,调用相应typeHandler,自动实现转换逻辑。下图为 mybatis 内置类型转换器,基本涵盖了所有 「Java/JDBC」 数据类型。
在这里插入图片描述

通用解决方案

自定义 typeHandler

下面我们来实现带有加解密功能的类型转换器,实现方式也比较简单,只要继承 org.apache.ibatis.type.BaseTypeHandler,重写相关方法。

简单起见,上述加解密仅使用了 Base64,大家可以替换成相应加解密算法即或者引入相应加解密服务。
在这里插入图片描述

其中加密转换将在 setNonNullParameter 中执行,解密转换将在 getNullableResult中执行。CryptTypeHandler 使用一个 MappedTypes 注解,包含一个 CryptType 类,这个类使用 mybatis 别名功能,可以极大简化 sqlmap 相关配置。
在这里插入图片描述

注册 typeHandler

使用方必须将 typeHandler 和 alias 注册到 mybatis 中,否则无法生效。下面提供三种方式,可以根据项目情况选择其中一种即可:
「单独使用 mybatis」
这种场景需要在 「mybatis-config.xml」 配置,mybatis 启动时将会加载该配置文件。

<typeHandlers><!--类型转换器包路径--><package name="com.xx.xx"/>
</typeHandlers><!-- 别名定义 -->
<typeAliases><!-- 针对单个别名定义 type:类型的路径 alias:别名 --><typeAlias type="xx.xx.xx" alias="xx"/>
</typeAliases>

「使用 Spring 配置 Mybatis Bean」

配合 Spring 使用时需要将 typeHandler 注入 SqlSessionFactoryBean ,配置方式如下:

<!-- MyBatis 工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource" /><!--alias 注入--><property name="typeAliasesPackage" value="xx.xx.xx"/><!--  typeHandlers 注入   --><property name="typeHandlersPackage" value="xx.xx.xx"/>
</bean>

「SpringBoot」

SpringBoot 方式就最简单了,只要引入 mybatis-starter,配置文件加入如下配置即可:

## mybatis 配置
# 类型转换器包路径
mybatis.type-handlers-package=com.xx.xx.x
mybatis.type-aliases-package=com.xx.xx

修改 mapper sql 配置

最后我们只要简单修改 mapper 中 resultMap 或 sql s配置就可以实现加解密。假设我们对现有一张 「bank_card」 表进行加解密,表结构如下:

CREATE TABLE bank_card (
id int primary key auto_increment,
gmt_create timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
gmt_update timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
card_no varchar(256) NOT NULL DEFAULT '' COMMENT '卡号',
phone varchar(256) NOT NULL DEFAULT '' COMMENT '手机号',
name varchar(256) NOT NULL DEFAULT '' COMMENT '姓名',
id_no varchar(256) NOT NULL DEFAULT '' COMMENT '证件号'
);

「insert 加密」
现需要对 card_no,phone,name,id_no 进行加密,「insert」 语句加密示例:

<insert id="insertBankCard" keyProperty="id" useGeneratedKeys="true" parameterType="org.demo.pojo.BankCardDO">INSERT INTO bank_card (card_no, phone,name,id_no)VALUES(#{card_no,javaType=crypt},#{phone,typeHandler=org.demo.type.CryptTypeHandler},#{name,javaType=crypt},#{id_no,javaType=crypt})
</insert>

我们只需要在 「#{}」 指定 typeHandler,传入参数最后将被加密。使用 typeHandler需要使用类的全路径,比较繁琐,我们可以使用 「javaType」 属性,直接使用上面我们的定义别名 「crypt」。数据库最终执行sql 如下:

INSERT INTO bank_card (card_no, phone,name,id_no) VALUES ('NjQzMjEyMzEyMzE=', 'MTM1Njc4OTEyMzQ=', '5rWL6K+V5Y2h', 'MTIzMTIzMTIzMQ==');

推荐一款 IDEA 的插件 「mybatis-log-plugin」,可以自动将 mybatis sql 日志还原成真实执行 sql

「查询加解密」普通查询解密示例如下:

<resultMap id="bankCardXml" type="org.demo.pojo.BankCardDO"><result property="card_no" column="card_no" typeHandler="org.demo.type.CryptTypeHandler"/><result property="name" column="name" typeHandler="org.demo.type.CryptTypeHandler"/><result property="id_no" column="id_no" typeHandler="org.demo.type.CryptTypeHandler"/><result property="phone" column="phone" typeHandler="org.demo.type.CryptTypeHandler"/>
</resultMap>
<select id="queryById" resultMap="bankCardXml">select * from bank_card where id=#{id}
</select>

这里我们在 「select」 配置中只能使用 resultMap 属性,指定 typeHandler 。数据库明文、密文共存的情况,查询解密示例如下:

<!-- resultMap 同上   -->
<select id="queryByPhone" resultMap="bankCardXml">select * from bank_card where phone in(#{card_no,javaType=crypt},#{card_no})
</select>

最后我们可以将自定义的 typeHandler 单独打包发布,其他业务方只需要引用,改造相关配置文件,即可完成数据加解密。上述代码示例已上传至 Github

总结
借助于自定义的 typeHandler,我们实现了一个通用的加解密的方案,该方案对于使用方来说代码侵入性小,开箱即用,可以快速完成加解密的改造。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/20156.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ARM32开发——LED点灯

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 点灯的两种方式灌入电流法输出电流法扩展板点灯点灯方式点亮LED1-4完整实现 点灯的两种方式 不同颜色LED&#xff0c;达到相同亮度…

[数据集][目标检测]猫狗检测数据集VOC+YOLO格式8291张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;8291 标注数量(xml文件个数)&#xff1a;8291 标注数量(txt文件个数)&#xff1a;8291 标注…

ETLCloud中如何使用Kettle组件

ETLCloud中如何使用Kettle组件在当今数据驱动的时代&#xff0c;数据处理和分析已成为企业决策的关键。为了更高效地处理海量数据&#xff0c;ETL&#xff08;Extract, Transform, Load&#xff09;工具变得至关重要。而在众多ETL工具中&#xff0c;Kettle作为一款开源、灵活且…

攻防实战 | 邮件高级威胁检测与自动化响应

历经三个月的时间&#xff0c;年度重磅直播节目Fortinet 2024年度“Demo季”近日终于迎来了备受瞩目的压轴大戏——Demo Day第三期&#xff0c;主题为《新邮件安全下的高级威胁检测与自动化响应》。继成功举办了前两期《企业网络中的多源威胁情报自动化整合与集成》和《应急响应…

Pycharm使用时的红色波浪线报错——形如‘break‘ outside loop

背景&#xff1a; 我在一个方法中&#xff0c;写了一个if判断&#xff0c;写了一个break&#xff0c;期望终止这个函数&#xff0c;编辑器出现报错 形如下图 视频版问题教程&#xff1a; Pycharm下出现波浪线报错&#xff0c;形如break outside loop 过程&#xff1a; 很奇…

IDEA一键启动多个微服务

我们在做微服务项目开发的时候&#xff0c;每次刚打开IDEA&#xff0c;就需要把各个服务一个个依次启动&#xff0c;特别是服务比较多时&#xff0c;逐个点击不仅麻烦还费时。下面来说一下如何一键启动多个微服务。 操作步骤 点击Edit Configurations 2.点击“”&#xff0c;…

【设计模式】JAVA Design Patterns——Facade(外观模式)

&#x1f50d;目的 为一个子系统中的一系列接口提供一个统一的接口。外观定义了一个更高级别的接口以便子系统更容易使用。 &#x1f50d;解释 真实世界例子 一个金矿是怎么工作的&#xff1f;“嗯&#xff0c;矿工下去然后挖金子&#xff01;”你说。这是你所相信的因为你在使…

性价比为王,物流商怎么选择高效的国际物流管理平台

在全球化贸易日益繁荣的今天&#xff0c;国际物流行业作为链接国内商家和海外市场的重要桥梁&#xff0c;发挥着极其重要的作用。 然而&#xff0c;随着国际物流市场竞争的加剧&#xff0c;对物流商来说&#xff0c;也面临着成本管控和效率提升的双重挑战。今天我们会重点探讨…

解决 DataGrip 2024.1.3 连接 Tdengine 时timestamp字段显示时区不正确问题

设置中找到该设置&#xff0c;将原来的设置 yyyy-MM-dd HH:mm:ss 修改为: yyyy-MM-dd HH:mm:ss.SSS z 即可。 注意&#xff1a;只能修改第一个,修改后提示错误&#xff0c;但是查询数据时能成功格式化时间&#xff0c;修改第二个不生效&#xff0c;可能是 bug 具体格式见: Date…

Opera 浏览器与Google联手,推出由Gemini驱动的全新AI功能

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

富士摄像机X-H2S MOV格式化后重新写入后的恢复方法

X-H2S是富士数码的一款旗舰机型&#xff0c;支持4K/6K高清&#xff0c;视频编码为最新的HVC。下面我们来看下富士数码摄像机恢复案例。 故障存储:512G存储卡 Exfat文件系统 故障现象: 512G的卡误格式化后又进行了拍摄&#xff0c;卡使用了120G不到的空间&#xff0c;其它底…

【EFK日志系统】docker一键部署kibana、es-head

docker一键部署kibana、es-head kibana部署es-head部署 上一篇文章搭建了es集群 规划服务器是 es01:172.23.165.185 es02:172.23.165.186 es03:172.23.165.187 那么kibana就搭建在主节点es01:172.23.165.185 按照顺序参考&#xff1a; docker一键部署EFK系统&#xff08;elas…

详解生成式人工智能的开发过程

回到机器学习的“古老”时代&#xff0c;在您可以使用大型语言模型&#xff08;LLM&#xff09;作为调优模型的基础之前&#xff0c;您基本上必须在所有数据上训练每个可能的机器学习模型&#xff0c;以找到最佳&#xff08;或最不糟糕&#xff09;的拟合。 开发生成式人工智能…

【linux】线程同步和生产消费者模型

线程同步 当我们多线程访问同一个临界资源时&#xff0c;会造成并发访问一个临界资源&#xff0c;使得临界资源数据不安全&#xff0c;我们引入了锁的概念&#xff0c;解决了临界资源访问不安全的情况&#xff0c;对于线程而言竞争锁的能力有强有弱&#xff0c;对于之前就抢到…

系统架构设计师【第9章】: 软件可靠性基础知识 (核心总结)

文章目录 9.1 软件可靠性基本概念9.1.1 软件可靠性定义9.1.2 软件可靠性的定量描述9.1.3 可靠性目标9.1.4 可靠性测试的意义9.1.5 广义的可靠性测试与狭义的可靠性测试 9.2 软件可靠性建模9.2.1 影响软件可靠性的因素9.2.2 软件可靠性的建模方法9.2.3 软件的可靠性模…

实物资产的市场主线将逐步回归

民生证券认为&#xff0c;投资者逐渐意识到长期趋势并没有发生变化&#xff0c;这或许正是本周最大的变化。在预期博弈重回冷静期后&#xff0c;去金融化背景下实物资源占优的市场主线也将逐步回归。 1 高低切换后的冷静期 从4月下旬至上周&#xff0c;A股市场呈现出由高位资产…

用windows server backup备份文件夹到网络共享文件夹并恢复

一、备份 开始 运行windows server backup,在右边的窗格中点击“备份计划” 选择备份配置 因为我们要备份的是一个文件夹&#xff0c;所以&#xff0c;选“自定义”&#xff0c;卷即为磁盘分区。 选择要备份的项 点击添加项目&#xff0c;可依次添加多个备份项目。 勾选需要…

汽车MCU虚拟化--对中断虚拟化的思考(2)

目录 1.引入 2.TC4xx如何实现中断虚拟化 3.小结 1.引入 其实不管内核怎么变&#xff0c;针对中断虚拟化无非就是上面两种&#xff0c;要么透传给VM&#xff0c;要么由Hypervisor统一分发。汽车MCU虚拟化--对中断虚拟化的思考(1)-CSDN博客 那么&#xff0c;作为车规MCU龙头…

Open3D(C++) Ransac拟合多项式曲线

目录 一、算法原理一、代码实现三、结果展示本文由CSDN点云侠原创,Open3D(C++) Ransac拟合多项式曲线,爬虫自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT生成的文章。 一、算法原理 RANSAC(Random Sample Consensus)是一种用于拟合模型的迭…

004 仿muduo实现高性能服务器组件_Buffer模块与Socket模块的实现

​&#x1f308;个人主页&#xff1a;Fan_558 &#x1f525; 系列专栏&#xff1a;仿muduo &#x1f339;关注我&#x1f4aa;&#x1f3fb;带你学更多知识 文章目录 前言Buffer模块Socket模块 小结 前言 这章将会向你介绍仿muduo高性能服务器组件的buffer模块与socket模块的实…