1. 什么是 SQL 注入?
jdbc程序执行时, sql语句在拼接时由页面传入参数,如果用户恶意传入一些sql中的特殊关键字,会导致sql语句意义发生变化,这种攻击方式就叫做sql注入。
2. 引子:
sql注入的危害: 黑客可以一行代码登录超管账户,对数据库造成不可挽回的损失。
参考用户注册登录案例:
// 用户登录验证(字符串拼接)
String sql = "select * from s_user where loginName = '"+ loginName +"' and loginPwd = '"+ loginPwd +"'";
// 正常用户:
用户名:admin
密 码:123456
可以正常登录
---------------------------------------------
// 恶意用户:
用户名: aaa
密 码: aaa' or '1'='1
也可以登陆成功, 这叫做SQL注入,
由于 恶意用户 输入的密码被当作sql语句,编译时,1=1,返回true, 所以验证通过,登陆成功。
3. 具体实例
下方的登录实例,运行成功后,后台输入恶意sql语句,就可以登陆成功。
public class JDBCTest02 {
public static void main(String[] args) {
// 初始化界面,(返回用户名/密码)
Map<String,String> userLoginInfo = initUi();
// 验证用户名和密码:(传入登录信息)
boolean loginSuccess = login(userLoginInfo);
// 登陆成功/失败 ==> 布尔值
// 最后输出结果
System.out.println(loginSuccess ? "登陆成功": "登陆失败");
}
/*
* 用户登录:
* @param userLoginInfo 用户登录信息
* @return false 登陆失败, true 登陆成功
*/
private static boolean login(Map<String, String> userLoginInfo) {
// JDBC 代码
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// 单独定义变量
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
// 打标记意识
boolean loginSuccess = false;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取数据库连接
// 使用时,把school改为你自己的数据库名
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root","root");
// 3. 获取数据库操作对象
stmt = conn.createStatement();
// 4. 执行SQL
// 注意: s_user是school里的用户表,记得改为你自己定义的的用户表!!
String sql = "select * from s_user where loginName = '"+ loginName +"' and loginPwd = '"+ loginPwd +"'";
rs = stmt.executeQuery(sql);
// 5. 处理结果集
if (rs.next()){
// 登陆成功
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 6. 释放资源
if (rs !=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt !=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn !=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// return false;
return loginSuccess;
}
/*
* 初始化用户界面
* @return 用户输入的用户名和密码等登录信息
*
*/
private static Map<String, String> initUi() {
Scanner s = new Scanner(System.in);
System.out.println("用户名: ");
String loginName = s.nextLine();
System.out.println("密码: ");
String loginPwd = s.nextLine();
// 用键值对存储输入信息
Map<String,String> userLoginInfo = new HashMap<>();
// 传值
userLoginInfo.put("loginName", loginName);
userLoginInfo.put("loginPwd", loginPwd);
return userLoginInfo;
}
}
4. 如何防止sql注入?
原理: 让用户输入信息不参与SQL语句的编译过程,问题就解决了。
即使用户提供的信息中含有SQL语句的关键字,但是无法参与编译,也不起作用。
这里引入 Statement 和 PreparedStatement
- 上面的案例使用了数据库操作对象 Statement ,让sql语句直接参与编译。
- SUN公司发明了 PreparedStatement 来让sql语句预编译,然后再传值,间接的阻止了恶意的sql注入。
5. PreperedStatement 相对 Statement 的优点:
- 没有SQL注入的问题。
- Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。
- 数据库和驱动可以对PreperedStatement进行优化(只有在相关联的数据库连接没有关闭的情况下有效)。
6. 总结
SQL注入虽然十几年前就被淘汰了,但是现在仍然有一些程序员偷懒,粗心大意,写了一些带有漏洞的代码,让黑客有机可乘,显得程序猿很不专业,奉劝在座的各位,耗子喂汁。