一、SQL注入
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
上述语句摘自百度百科,可能对于有些人来说晦涩难懂,那么我举个最简单的例子,来体验这一过程;
假设有一个登录表单,后台数据校验sql为select * from user where username = 'XXX' and password = 'XXX'
,XXX为传入的用户名和密码,根据sql返回的结果判断登录是否成功;(方便解释,简单处理)
这条sql是预先拼接好然后提交给数据库执行的,假设我们把password
的内容改为1' or '1' = '1
,注意里面的单引号!如果登录名为caojiantao
,那么最终生成的sql便是:
select * from user where username = 'caojiantao' and password = '1' or '1' = '1'
!!这样一来不用知道用户caojiantao
的密码也能够登录成功了,更有甚者,在密码处输入"1';drop table user;"
,直接删除了数据表,十分的危险。
这就是一个最简单sql注入的例子,输入包含sql命令的内容,欺骗服务器执行破坏数据。
二、Statement
JDBC核心接口,对象用于将SQL语句发送到数据库中。一个简单的demo演示Statement的使用;
public static void main(String[] args) {
// 数据库配置
String url = "jdbc:mysql://127.0.0.1/ssm";
String name = "com.mysql.jdbc.Driver";
String user = "root";
String password = "Cjt00382114.";
// 登录账号密码
String username = "caojiantao";
String pwd = "123";
// 拼接sql
String sql = "select * from user where username = '" + username + "' and password = '" + pwd + "'";
try {
// jdbc操作
Class.forName(name);
Connection connection = DriverManager.getConnection(url, user, password);
// Statement
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
System.out.println(resultSet.getString("nickname"));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
代码很简单,判断用户名和密码是否匹配,匹配则输出用户的nickname
昵称;
根据上面sql注入的问题,我们试着把pwd传入1' or '1' = '1
,前面的1可以随便填,运行程序可以发现输出了用户昵称曹建涛
,说明sql注入成功,那么,我们该怎么避免呢?
三、PreparedStatement
采用字符串匹配?筛选sql命令字符串?没那么麻烦,JDBC已经有现成的处理方案了,那就是PreparedStatement
;
PreparedStatement
继承自Statement
,字面可译为预声明,强调一个预,内部包含一个预编译的sql语句,参数采用占位符?
进行填充,还是看一段代码体会下;
public static void main(String[] args) {
// 数据库配置
String url = "jdbc:mysql://127.0.0.1/ssm";
String name = "com.mysql.jdbc.Driver";
String user = "root";
String password = "Cjt00382114.";
// 登录账号密码
String username = "caojiantao";
String pwd = "123";
String preSql = "select * from user where username = ? and password = ?";
try {
// jdbc操作
Class.forName(name);
Connection connection = DriverManager.getConnection(url, user, password);
// PreparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(preSql);
preparedStatement.setString(1, username);
preparedStatement.setString(2, pwd);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString("nickname"));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
我们可以修改pwd内容为1' or '1' = '1
,运行程序没有任何输出,说明PreparedStatement
有效地避免了sql注入问题;
因为SQL语句在程序运行前已经进行了预编译,在程序运行时第一次操作数据库之前,SQL语句已经被数据库分析,编译和优化,对应的执行计划也会缓存下来并允许数据库已参数化的形式进行查询,当运行时动态地把参数传给PreprareStatement时,即使参数里有敏感字符如 or '1=1'也数据库会作为一个参数一个字段的属性值来处理而不会作为一个SQL指令,如此,就起到了SQL注入的作用了!
上述摘自java中预处理PrepareStatement为什么能起到防止SQL注入的作用??!!
因为是继承关系,因而Statement
具备的PreparedStatement
全都有,而且PreparedStatement
还具备独有的预处理功能,相比Statement
,PreparedStatement
好处有三:
- 提高代码的可读性,便于维护;
- 提高了sql执行效率;
- 增强了安全性,避免sql注入;
四、花絮——myBatis的 # 和 $
# 相当于对数据 加上 双引号,$ 相当于直接显示数据。
一句话言简意赅。分条陈述:
- # 能够sql注入问题,$ 不行;
- $ 用于传入数据库对象,例如表名;
- 能用 # 就不要使用 $;
参考文章:mybatis中的#和$的区别
网友评论