Mybatis数据加密解密

news2024/10/6 22:31:08

文章目录

      • 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 {

    @Autowired
    private IEncryptDecrypt encryptDecrypt;

    @Override
    public 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();
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public 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 {

    @Autowired
    private IEncryptDecrypt encryptDecrypt;

    @Override
    public 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;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public 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.coloradmin.cn/o/1721294.html

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

相关文章

注意力可视化代码

读取网络层输出的特征到txt文件&#xff0c;arr为文件名 def hot(self, feature, arr):# 在第二维&#xff08;通道维&#xff09;上相加summed_tensor torch.sum(feature, dim1, keepdimTrue) # 结果形状为 [1, 1, 64, 64]selected_matrix summed_tensor.squeeze(1) # 移除…

【RS】哨兵系列新网站无法下载的问题及解决办法(Sentinel-2)

最近有些小伙伴留言说哨兵数据无法下载&#xff0c;网站打开后会有一层蒙版&#xff0c;无法选取研究区等信息&#xff0c;今天就跟大家分享一下如何解决这个问题。还知道如何下载的小伙伴可以移步到之前的文章&#xff1a;【RS】欧空局Sentinel-2卫星数据下载(哨兵1、2、3、5P…

shopee签名x-sap-ri、x-sap-sec算法还原

最新版签名&#xff0c;免账号登录成功率百分百&#xff0c;需要可d 两种方式base64 MTQzMDY0OTc3OA QXVndXN0MjItZnF4

JS【详解】快速排序

快速排序的时间复杂度为 O(n2) 排序流程 1、首先设定一个分界值&#xff08;比如数组最中间的元素&#xff09;&#xff0c;通过该分界值将数组分成左右两部分。 2、将大于或等于分界值的数据集中到数组右边&#xff0c;小于分界值的数据集中到数组的左边。 3、对左侧和右侧的…

【网络层】IP地址基础 与 子网掩码

文章目录 IP地址基础IP地址概念IP地址分类公网地址和私网地址 子网掩码子网掩码作用默认子网掩码网络地址、主机地址、广播地址 IP地址基础 IP地址概念 IP地址&#xff1a;IP Address 在网络中&#xff0c;通信节点都需要有一个IP地址 IP地址以点分十进制表示&#xff0c;有…

【QEMU中文文档】1.1 支持的构建平台

本文由 AI 翻译&#xff08;ChatGPT-4&#xff09;完成&#xff0c;并由作者进行人工校对。如有任何问题或建议&#xff0c;欢迎联系我。联系方式&#xff1a;jelin-shoutlook.com。 原文&#xff1a;Supported build platforms — QEMU documentation QEMU 旨在支持在多个主机…

prometheus docker部署

1.安装Docker sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors":["https://hub-mirror.c.163.com"] } EOF export DOWNLOAD_URL"https://hub-mirror.163.com/docker-ce" curl -fsSL https://ge…

携手亚马逊云科技,神州泰岳如何打通生成式AI落地最后三公里

导读&#xff1a;神州泰岳成为首批获得亚马逊云科技生成式AI能力认证的合作伙伴。 “过去6年来&#xff0c;在与亚马逊云科技的合作过程中&#xff0c;我们大概签约了300家以上的中国出海企业。”近日在一次沟通会上&#xff0c;神州泰岳副总裁兼云事业部总经理刘家歆这样向媒…

springboot编写日志环境搭建过程

AOP记录日志 AOP记录日志的主要优点包括&#xff1a; 1、低侵入性&#xff1a;AOP记录日志不需要修改原有的业务逻辑代码&#xff0c;只需要新增一个切面即可。 2、统一管理&#xff1a;通过AOP记录日志可以将各个模块中需要记录日志的部分进行统一管理&#xff0c;降低了代…

Linux 深入讲解自动化构建工具

各位大佬好 &#xff0c;这里是阿川的博客 &#xff0c; 祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 Linux一系列的文章&#xff08;质量分均在93分…

水滴式粉碎机:多功能饲料粉碎设备

饲料粉碎机是一种专门用于将各种饲料原料进行粉碎处理的机械设备。无论是玉米、小麦等谷物&#xff0c;还是豆粕、鱼粉等动物性原料&#xff0c;甚至是一些粗纤维含量较高的秸秆、牧草等&#xff0c;都可以经过饲料粉碎机的处理&#xff0c;变成适合畜禽消化吸收的精细饲料。这…

solr-8.11.3

https://solr.apache.org/downloads.html https://archive.apache.org/dist/solr/solr/ F:\Document_Solr.apache.org\solr-8.11.3\bin Microsoft Windows [版本 10.0.19045.2965] (c) Microsoft Corporation。保留所有权利。 C:\Users\Administrator>F: F:\> F:\>…

AI播客下载:a16z (主题为AI、web3、生物技术等风险投资)

a16z播客是一个综合性的科技和创新领域的媒体平台&#xff0c;通过多种节目形式和丰富的内容&#xff0c;为广大听众提供了一个了解最新科技趋势和创新思维的窗口。a16z播客是由安德里森霍罗威茨&#xff08;Andreessen Horowitz&#xff0c;简称a16z&#xff09;推出的一个科技…

计算机毕业设计hadoop+spark知识图谱课程推荐系统 课程预测系统 课程大数据 课程数据分析 课程大屏 mooc慕课推荐系统 大数据毕业设计

本科毕业设计&#xff08;论文&#xff09; 题目&#xff1a;基于 Hadoop和Spark的课程推荐系统的设计与实现 烟台南山学院教务处 二〇二四年六月 院 系&#xff1a;科技与数据学院数据科学与软件工程系 专 业&#xff1a;数据科学与大数据技术 班 级&#xff1a;数…

11Linux学习笔记

Linux 实操篇 目录 文章目录 Linux 实操篇1.rtm包&#xff08;软件&#xff09;1.1 基本命令1.2 基本格式1.3安装rtm包1.4卸载rtm包 2.apt包2.1 基本命令结构2.2 常用选项2.3常用命令 1.rtm包&#xff08;软件&#xff09; 1.1 基本命令 1.2 基本格式 1.3安装rtm包 1.4卸载r…

Golang | Leetcode Golang题解之第123题买卖股票的最佳时机III

题目&#xff1a; 题解&#xff1a; func maxProfit(prices []int) int {buy1, sell1 : -prices[0], 0buy2, sell2 : -prices[0], 0for i : 1; i < len(prices); i {buy1 max(buy1, -prices[i])sell1 max(sell1, buy1prices[i])buy2 max(buy2, sell1-prices[i])sell2 m…

如何让数据标注

1.用Anacoda创建一个新的虚拟环境 2.进入虚拟环境 conda activate stu_data&#xff08;就是刚才创建的虚拟变量的名称&#xff09; 3.在此环境中安装labelimg pip install labelimg 4.进入labelimg 直接输入 labelimg 快捷键&#xff1a;D&#xff1a;下一个图片 A&#xff1a…

深度学习设计模式之装饰器模式

文章目录 前言一、介绍二、详细分析1.核心组成2.实现步骤3.代码示例4.优缺点优点缺点 5.使用场景 总结 前言 装饰器模式属于结构型模式&#xff0c;又叫包装设计模式&#xff0c;动态的将责任添加到对象上。 一、介绍 装饰器模式又叫包装设计模式&#xff0c;为现有的类的一个…

四川音盛佳云电子商务有限公司引领抖音电商新风潮

在数字化浪潮席卷全球的今天&#xff0c;电商行业已成为推动经济发展的重要力量。作为这一领域的佼佼者&#xff0c;四川音盛佳云电子商务有限公司凭借其在抖音电商服务领域的专业实力和独特视角&#xff0c;正引领着行业的新风潮&#xff0c;助力品牌实现快速增长和腾飞。 四…