文章目录
一. Statement对象
- 说明:SQL注入只对SQL语句的编译过程有破坏作用
- 代码
public static void login(String username,String password){
Statement st = conn.createStatement();
String sql = "select * from user where name ='"+username+"' AND password = '"+ password+"'";
ResultSet rs = st.executeQuery(sql);
// ...
}
- 正确登录方式:
login("admin","123456")
- 对应SQL:
select * from user where name ='admin' AND password= '123456'
- 对应SQL:
- SQL注入方式:
login("'or '1=1","'or '1=1")
- 对应SQL:
select * from user where name = '' or '1=1' and password='' or '1=1'
- 由于
1=1
永远成立,即where子句总为真
- 对应SQL:
总结:Statement对象,默认直接执行SQL语句,对特殊字符不转义处理,导致SQL拼接出现漏洞。
二. preparedStatement对象
- 概述:
preparedStatement
是Statement
的子类,它会预编译SQL语句,使用?
占位符代替参数,表示此处有待输入的变量,最后通过set方式来填值。该对象可以防止SQL注入,并且效率更高。 - 代码
public static void login(String username,String password){
String sql = "select * from user where name =? AND password=?";
PreparedStatement st = conn.prepareStatement(sql);
st.setString(1, username);
st.setString(2, password);
ResultSet rs = st.executeQuery();
//...
}
总结:preparedStatement把传递进来的参数当做字符串(使用引号包裹)。假设其中存在转义字符,比如说引号’'就会被直接转义,不再对sql语句进行解析编译,因此也就避免了sql注入问题。
三. MyBatis是否可以防止SQL注入
1. MyBatis中SQL注入的问题
- 示例代码
public interface UserDao{
@Select("SELECT name,password FROM user WHERE name=${name}")
List<User> getUserById(String name);
}
@Test
public void getUserById() {
// 1.正常查询
//String name = "'admin'";
// 2.SQL注入查询
String name ="''or '1=1'";
List<User> users = userMapper.getUserById(name);
users.forEach(System.out::println);
}
- 执行的SQL语句
SELECT name,password FROM user WHERE name='admin';
SELECT name,password FROM user WHERE name=''or '1=1';

总结:在MyBatis中,
${xxxx}
格式的参数会直接参与SQL编译,从而不能避免注入攻击。
2. MyBatis中解决SQL注入
总结:把SQL语句中参数格式由
${xxx}
变为#{xxx}
,在MyBatis中可防止SQL注入。
原因:因为MyBatis启用了预编译功能,在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,来替换占位符“?” ,这样就不会出现注入问题。
底层原理:在Mybatis框架底层,是JDBC中的PreparedStatement类在起作用。不仅能提高安全性,而且在多次执行同一个SQL时,使用已编译好的SQL能够提高效率。
- 示例代码
@Select("SELECT name,password FROM user WHERE name=#{name}")
List<User> getUserById(String name);
}
- 执行结果:不管输入任何参数,SQL语句都为
SELECT name,password FROM user WHERE name=?
。SQL语句中参数不会出现,而是使用?
占位符代替。


三. 必须使用${}
格式的情况
总结:在涉及到动态表名和列名时,必须使用
${xxx}
。
- 动态表名和列名的示例
select * from #{table} where #{column} =1;
- 示例代码1:使用
#{xxx}
,则有查询结果,但降序并没有生效。
@Select("select * from user order by #{id} desc")
List<User> getUserInfo(Long id);
- 示例代码2:使用
${xxx}
,则降序生效
@Select("select * from user order by ${id} desc")
List<User> getUserInfo(Long id);
- 原因:如果使用#,是按string类型拼接,SQL语句变为
order by 'id值' desc
;如果使用$,则SQL语句为order by id desc
。
四. 总结
#{}
:相当于JDBC中的PreparedStatement,会预编译;解析传过来参数带单引号;不存在SQL注入问题。${}
:未经过预编译;参数直接填充到SQL中,即解析出来的参数值不带单引号;存在SQL注入问题。- 使用MyBatis框架编写SQL语句时,尽量采用
#{xxx}
格式来做参数的占位。 - 在涉及到动态表名和列名时,只能使用
${xxx}
这样的参数格式占位。这种不得不使用的情况下,我们要手工做过滤工作,这样才能有效防止SQL注入攻击。