mybatis 防sql注入

557 阅读2分钟

       使用过mybatis的程序员应该都知道,在写sql时,遇到变量时会用 #{变量名}, 也知道这样可以防止sql注入,今天看看原理,做个笔记(主看最中心原理)。

首先说结论:mybatis底层依赖的是 java.sql.PreparedStatement 实现防注入的。

                                                              图0 官方文档部分截图

在这里调用了 java.sql.Connection.prepareStatement 方法创建 PrepareStatement, 这样可以把sql发送给数据库, 数据库会对sql的进行预编译,

直白一点, sql预编译后,就不会发生sql注入的情况,例如:

select * from student where id = #{num, jdbctype = VARCHAR}

经过预编译后,把 

num = 'no1'; delete from student where 1=1; 传入到sql中

这时数据库就会认为 'no1'; delete from student where 1=1; 是一个字符串,也就是把内部的特殊字符转义了,即

select * from student where id = '\'no1\'; delete from student where 1=1;'

而不是结合后的两个sql

select * from student where id = 'no1'; delete from student where 1=1;

mybatis源码关于防止注入部分可以分两部分;

1. 解析sql

  1. 执行sql

1. 解析sql

                                                                            图1

根据图1,mybatis在扫描到sql后,会把#{} 从sql字符串中替换成 ?,同时也会根据接口方法的参数类型来记录sql的参数类型。

2. 执行sql

                                                                     图2

根据图2,当执行mapper接口中的方法时,会根据方法的全路径名匹配到sql,匹配成功后,会将sql(图2中的params[0])传入到 java.sql.Connection.prepareStatement 方法中,这样就实现了sql的预编译。

                                                                     图3

根据图3,在向sql中注入变量值时,mybatis会根据 Mapper中的方法参数类型和值,最终映射到 PreparedStatement 中的 setInt、setString。。等方法设置sql参数。(例如图3调用的是getSubject(final int id)方法,所以最终会调用PreparedStatement的setInt方法)。设置完参数后提交声明到数据库。

以上就是mybatis防注入的原理和简要流程。

3. 如果sql使用 ${}时;

                                                                    图4

根据图4,当sql使用 select * from subject where id = ${id},则在调用mapper方法时, 会直接把sql中的占位符替换成变量,因为这里是直接进行替换,而没有预编译,所以会有sql注入的风险。

4. 如果sql一起使用 #{} 和 ${} 时

在解析sql阶段时,直接按照${} 不对sql进行处理

                                                                     图5

根据图5,在执行sql阶段,会先解析${},再解析#{}

                                                               图6 先解析${}

                                                               图7 后解析#{}

5. 综合以上:

mybatis对代码中的sql语句的处理步骤:

5.1 在初始化阶段, 

先扫描,如果存在,则先跳过;如果不存在{}, 如果存在,则先跳过;如果不存在{},则继续处理#{},并将#{}替换成 ?

5.2 执行sql阶段

5.2.1 扫描,如果存在,则直接对sql字符串中的{}, 如果存在,则直接对sql字符串中的{} 替换成相应的变量值(因为是字符串替换,所以这里就存在sql注入问题),否则跳过本步骤;

5.2.2 扫描#{}, 如果存在(只有当${} 和 #{}同时存在一个sql中时),则将#{}替换成?,否则跳过本步骤

5.2.3 (如果5.2.2存在)设置变量值, 使用PreparedStatement 执行sql