1.SQL防注入
mybatis语句中要使用 #{xxx}防止SQL注入,${xxx}只是简单替换占位符,有注入的风险
例子:
1.1
"getNameByUserId" resultType="String"> SELECT name FROM user where id = #{userId}
可以看到输入的参数是String类型的userId,当我们传入userId="34;drop table user;"后,打印的语句是这样的:
select name from user where id = ?
这就得益于mybatis在底层实现时使用预编译语句。数据库在执行该语句时,直接使用预编译的语句,然后用传入的userId替换占位符?就去运行了。不存在先替换占位符?再进行编译的过程,因此SQL注入也就没有了生存的余地了。
mybatis是如何做到sql预编译的呢?其实框架底层使用的正是PreparedStatement类。PreparedStaement类不但能够避免SQL注入,因为已经预编译,当N次执行同一条sql语句时,节约了(N-1)次的编译时间,从而能够提高效率。
【底层实现原理】
其原因就是:采用了JDBC的PreparedStatement,就会将sql语句:“select id,no from user where id =?” 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析sql命令,比如select,from,where,and,or,order by等等。所以即使你后面输入了这些sql命令,也不会被当成sql命令来执行了,因为这些SQL命令的执行,必须先得通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为SQL命令来执行的,只会被当做字符串字面值参数。所以的sql语句预编译可以防御SQL注入。而且在多次执行同一个SQL时,能够提高效率。原因是SQL已编译好,再次执行时无需再编译。
1.2
如果将上面的语句改成:
"getNameByUserId" resultType="String"> SELECT name FROM user where id = ${userId} -- 注意与1.1的对比
当我们输入userId="34;drop table user;"后,打印的语句是这样的:
select name from user where id = 34;drop table user;
mybatis没有使用预编译语句,它会先进行字符串拼接再执行编译,这个过程正是SQL注入生效的过程。
在MyBatis中,“ $ {xxx}”这样格式的参数会直接参与SQL 编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用$ {xxx}“这样的参数格式。所以,这样的参数需要我们在代码中手工进行处理来防止注入。
#{}:相当于JDBC中的PreparedStatement的
$ {}:是输出变量的值
简单说,
# {}是经过预编译的,是安全的 ;
$ {}是未经过预编译的,仅仅是取变量的值,是非安全的,存在SQL注入。