sql注入现象:
1、先用一个程序引出sql注入现象:
例如:模拟用户登录业务。
测试代码:
package sql_inject;
import java.util.HashMap;
import java.util.Map;
import java.sql.*;
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) {
// 用户信息初始化
Map<String, String> userInfo = initUI();
// 检验用户名和密码
boolean loginSuccess = login(userInfo);
// 处理检验结果
System.out.println(loginSuccess ? "登录成功" : "登录失败");
}
// 用户信息初始化
private static Map<String, String> initUI() {
Scanner sc = new Scanner(System.in);
System.out.print("请输入用户名:");
String userName = sc.nextLine();
System.out.print("请输入密码:");
String password = sc.nextLine();
Map<String, String> map = new HashMap<>();
map.put("userName", userName);
map.put("password", password);
return map;
}
// 检验用户名和密码
private static boolean login(Map<String, String> userInfo) {
// 定义标记,检验的结果
boolean loginSuccess = false;
// 获取登录信息中的用户名,密码
String loginName = userInfo.get("userName");
String loginPwd = userInfo.get("password");
// 连接数据库的配置信息
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/study_test";
String user = "root";
String password = "666666";
Connection conn = null;
Statement stmt = null;
ResultSet result = null;
try {
// 1、注册驱动
Class.forName(driver);
// 2、获取连接
conn = DriverManager.getConnection(url, user, password);
// 3、获取数据库操作对象
stmt = conn.createStatement();
// 4、执行sql语句
String sql = "select * from t_user where username = '"+ loginName +"' and pwd = '"+ loginPwd +"'";
result = stmt.executeQuery(sql);
// 5、处理查询结果集
if (result.next()) {
loginSuccess = true;
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 6、释放资源
if (result != null) {
try {
result.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return loginSuccess;
}
}
当前程序存在的问题(用下列信息也能登陆成功):
用户名:abc
密码:abc’ or ‘1’='1
登录结果:登录成功
即拼接的sql语句为:select * from t_user where username = ‘abc’ and pwd = ‘abc’ or ‘1’=‘1’;
这条语句恒成立,因为条件使用了or true。
这种现象被称为:sql注入现象。(黑客经常使用)
sql注入现象的根本原因是什么?
用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程。导致sql语句的原意被扭曲,进而达到sql注入。
2、怎么解决sql注入现象?
(1)只要用户输入的内容不参与sql的编译过程,问题就得到了解决。
即使用户输入的信息含有sql关键字,没有参与编译过程,不起作用。
(2)具体怎么操作代码才能解决sql注入问题?
只需将数据库操作对象接收的类型Statement换成PreparedStatement即可。
PreparedStatement接口继承了Statement接口,同样也在java.sql.*包下。
PreparedStatement属于预编译的数据库操作对象。
它的原理是:预先对sql语句的框架进行编译,然后再给sql语句传值。
具体操作代码为:
// 第3步:获取预编译的数据库连接对象
String sql = “select username, pwd from t_user where username = ? and pwd = ?”;
PreparedStatement ps = conn.prepareStatement(sql);
// 给占位符传值(JDBC中所有的下标从1开始,第1个问号的下标是1)
ps.setString(1, loginName);
ps.setString(2, loginPwd);
// 如果是其他类型的值,只需要调用ps的setInt(), setDouble()等方法。
// 第4步:执行sql语句
ResultSet result = ps.executeQuery(); // 注意没有参数,已经指定了sql语句
其中sql语句中的?属于占位符,一个占位符一个值。
注意:不要写成了’?’, 这个属于单个字符。
测试结果:
用户名:abc
密码:abc’ or ‘1’='1
登录结果:登录失败
测试代码:
package sql_inject;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Test02 {
public static void main(String[] args) {
// 用户信息初始化
Map<String, String> userInfo = initUI();
// 检验用户名和密码
boolean loginSuccess = login(userInfo);
// 处理检验结果
System.out.println(loginSuccess ? "登录成功" : "登录失败");
}
// 用户信息初始化
private static Map<String, String> initUI() {
Scanner sc = new Scanner(System.in);
System.out.print("请输入用户名:");
String userName = sc.nextLine();
System.out.print("请输入密码:");
String password = sc.nextLine();
Map<String, String> map = new HashMap<>();
map.put("userName", userName);
map.put("password", password);
return map;
}
// 检验用户名和密码
private static boolean login(Map<String, String> userInfo) {
// 定义标记,检验的结果
boolean loginSuccess = false;
// 获取登录信息中的用户名,密码
String loginName = userInfo.get("userName");
String loginPwd = userInfo.get("password");
// 连接数据库的配置信息
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/study_test";
String user = "root";
String password = "666666";
Connection conn = null;
PreparedStatement ps = null;
ResultSet result = null;
try {
// 1、注册驱动
Class.forName(driver);
// 2、获取连接
conn = DriverManager.getConnection(url, user, password);
// 3、获取数据库操作对象
String sql = "select * from t_user where username = ? and pwd = ?";
ps = conn.prepareStatement(sql); // 绑定需要预编译的语句
// 给占位符指定值
ps.setString(1, loginName);
ps.setString(2, loginPwd);
// 4、执行sql语句
result = ps.executeQuery();
// 5、处理查询结果集
if (result.next()) {
loginSuccess = true;
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 6、释放资源
if (result != null) {
try {
result.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return loginSuccess;
}
}
(3)对比Statement和PreparedStatement用在登录业务上面的区别:
1、Statement存在sql注入现象,PreparedStatement解决了sql注入问题。
2、Statement是编译一次,执行一次,因为每个人的账号和密码都不一样,sql语句不一样。
PreparedStatement是编译一次,执行n次,采用预编译,语句没改动,不用持续编译,效率较高。
3、PreparedStatement在编译阶段,会对传值的类型进行安全检查。
综上所述:PreparedStatement使用较多,只有极少数情况才用Statement。
4、那什么情况使用Statement呢?
业务要求使用sql注入的时候,此时必须使用Statement。
例如,有个按钮的功能是:按照商品价格升序。
此时会在sql语句的最后面拼接关键字:order by price desc。
如果使用的是PreparedStatement的话,在传值的时候出现的问题:
ps.getString(1, “desc”);
因为desc是一个字符串,只能用getString传值,那么最后sql语句变为:order by price ‘desc’。
这样的语句能执行吗?显然不行。
3、练习:PreparedStatement完成增删改。
测试代码:
package sql_inject;
import java.sql.*;
public class Test03 {
public static void main(String[] args) {
// 先准备连接数据库的配置信息
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/study_test";
String user = "root";
String password = "666666";
Connection conn = null;
PreparedStatement ps = null;
try {
// 1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2、获取连接
conn = DriverManager.getConnection(url, user, password);
// 3、获取预处理数据库操作对象
/*String sql = "insert into dept(deptno, dname, loc) values(?, ?, ?)";
ps = conn.prepareStatement(sql);
ps.setInt(1, 50); // 部门编号
ps.setString(2, "人事部"); // 部门名称
ps.setString(3, "北京"); // 部门位置*/
/*String sql = "update dept set loc = ? where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, "天津");
ps.setInt(2, 50);*/
String sql = "delete from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, 50);
// 4、执行sql语句
int line = ps.executeUpdate();
System.out.println(line == 1 ? "成功" : "失败");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 6、释放资源
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}