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作为一款开源、灵活且…

c#面向对象:接口详解

接口&#xff08;interface&#xff09; 抽象类中的抽象方法只规定了不能是 private 的&#xff0c;而接口中的“抽象方法”只能是 public 的。这样的成员访问级别就决定了接口的本质&#xff1a;接口是服务消费者和服务提供者之间的契约。既然是契约&#xff0c;那就必须是透…

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

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

【持久层】在Spring Boot中使用Hibernate和Gradle构建项目

Hibernate是一个广泛使用的Java持久化框架&#xff0c;它使得Java对象与关系数据库之间的映射变得简单高效。在Spring Boot应用中&#xff0c;结合Gradle构建工具&#xff0c;能够方便地集成和使用Hibernate。本文将简述如何在Spring Boot中使用Hibernate&#xff0c;并通过Gra…

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…

DOS编程入门:探索基础、深入技巧与实战应用

DOS编程入门&#xff1a;探索基础、深入技巧与实战应用 DOS编程&#xff0c;作为计算机编程的基石之一&#xff0c;对于初学者来说&#xff0c;既是一种挑战&#xff0c;也是一次深入了解计算机底层运作的绝佳机会。本文将从四个方面、五个方面、六个方面和七个方面&#xff0…

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

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

JavaScript循环语句

JavaScript中的循环语句有三种&#xff1a;for循环、while循环和do...while循环。这些循环语句可以帮助我们重复执行一段代码&#xff0c;直到满足某个条件为止。 for循环&#xff1a; for循环是最常用的循环语句之一&#xff0c;它包含一个初始化表达式、一个条件表达式和一个…

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

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

【副业】12种程序员副业大汇总

1&#xff1a;写博客。技术能力可以的写技术文章输出&#xff0c;比如当前网站、掘金、阿里云社区、腾讯云社区、其他社区。 2&#xff1a;卖课程。大厂高P跟知识付费平台合作、录课卖课程、比如极课时间、慕课网、腾讯课堂。 3&#xff1a;写书。技术大拿出书&#xff0c;挣稿…

简述Vue中同时发送多个请求怎么操作?

在Vue中同时发送多个请求&#xff0c;我们通常使用axios这个库&#xff0c;因为它基于Promise&#xff0c;可以很好地处理异步操作。以下是两种常用的方法来同时发送多个请求&#xff1a; 方法一&#xff1a;使用Promise.all() 定义多个请求&#xff1a; 使用axios.get()或axi…

【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…

使用 Vue 3 和 JsBarcode 开发一维码显示组件

在现代前端开发中&#xff0c;条形码&#xff08;或称一维码&#xff09;在许多应用场景中非常常见&#xff0c;例如商品管理、物流跟踪等。本文将介绍如何使用 Vue 3 和 JsBarcode 库来创建一个灵活的一维码显示组件&#xff0c;并展示如何在应用中使用它。 1. 安装必要的依赖…

简述Vue 的响应式原理中 Object.defineProperty 有什 么缺陷 ?

Vue.js 2.x 的响应式原理主要依赖于 Object.defineProperty 方法来实现数据劫持&#xff0c;即当数据发生变化时&#xff0c;能够触发视图更新。然而&#xff0c;Object.defineProperty 方法在 Vue 的响应式系统中存在一些缺陷&#xff1a; 无法监听数组的变化&#xff1a; Obj…