准備數據
1、設計表
2、加入相關信息
代碼
package jdbc;
import java.sql.*;
import java.util.ResourceBundle;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class simpLogin {
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) {
boolean loginSuccess = false;
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
//JDBC代碼
ResourceBundle bundle = ResourceBundle.getBundle("jdbc.info");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();
String sql = "select * from user where loginName='" +loginName +"' and loginPwd='" +loginPwd+ "'";
rs = stmt.executeQuery(sql);
while (rs.next()){
loginSuccess = true;
}
}catch (SQLException | ClassNotFoundException e){
e.printStackTrace();
}finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return loginSuccess;
}
/**
* 初始化用戶界面
* @return 用戶輸入的用戶名和密碼等登錄信息
*/
private static Map<String, String> initUI() {
Scanner in = new Scanner(System.in);
System.out.println("用戶名:");
String loginName = in.nextLine();
System.out.println("密碼:");
String loginPwd = in.nextLine();
Map<String, String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName", loginName);
userLoginInfo.put("loginPwd", loginPwd);
return userLoginInfo;
}
}
SQL注入現象
示例
用戶名輸入:aaa
密碼輸入:aaa' or '1'='1
還是會顯示登錄成功,這就產生了SQL注入現象。所以當前程序存在安全隱患。
原因
1、Debug:
select * from user where
loginName='aaa' and
loginPwd='aaa' or '1'='1'
可以看出SQL語句完全被修改了。
2、在數據庫查詢:
3、分析
String sql = "select * from user where loginName='" +loginName +"' and loginPwd='" +loginPwd+ "'";
rs = stmt.executeQuery(sql);
以上代碼先完成了SQL語句的拼接,再發送SQL語句給DBMS進行SQL編譯。(先拼接,再編譯)
在拼接的時候將用戶提供的“非法信息”編譯進去,導致了原SQL語句的含義被扭曲了。
4、根本原因
用戶輸入的信息中含有SQL語句的關鍵字,並且這些關鍵字參與SQL語句的編譯過程,導致SQL語句的原意被扭曲,進而達到SQL注入。
解決SQL注入問題
1、只要用戶提供的信息不參與SQL語句的編譯過程,問題就解決了。即使用戶提供的信息中含有SQL語句的關鍵字,但是沒有參與編譯,不起作用。
2、要想用戶信息不參與SQL語句的編譯,那么必須使用java.sql.PreparedStatement
,該接口繼承了java.sql.Statement
3、PreparedStatement是屬於預編譯的數據庫操作對象。原理是:預先對SQL語句的框架進行編譯,然后再給SQL語句傳"值"。
4、幫助文檔
public interface PreparedStatement extends Statement
表示預編譯的SQL語句的對象。
SQL語句已預編譯並存儲在PreparedStatement對象中。 然后可以使用該對象多次有效地執行此語句。
注意:setter方法( setShort , setString用於設置IN參數值必須指定與所定義的SQL類型的輸入參數的兼容的類型,等等)。 例如,如果IN參數具有SQL類型INTEGER ,則應使用方法setInt 。
如果需要任意參數類型轉換,方法setObject應與目標SQL類型一起使用。
在設定的參數的以下示例中, con表示一個活動連接:
PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES
SET SALARY = ? WHERE ID = ?");
pstmt.setBigDecimal(1, 153833.00)
pstmt.setInt(2, 110592)
解決SQL注入代碼示例
package jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;
/**
* 解決SQL注入問題
*/
public class simpLoginPlus {
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) {
boolean loginSuccess = false;
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
//JDBC代碼
ResourceBundle bundle = ResourceBundle.getBundle("jdbc.info");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
/*這相當於SQL語句的架子。
其中一個問號(?)表示一個占位符,
一個?將來接收一個"值"。
注意:占位符不能使用單引號括起來。*/
String sql = "select * from user where loginName= ? and loginPwd= ?";
/*程序執行到此處,會發送SQL語句架子給DBMS,
然后DBMS進行SQL語句的預先編譯。*/
pstmt = conn.prepareStatement(sql);
/*給占位符?傳值,
* 第一個?下標是1,
* 第二個?下標是2*/
pstmt.setString(1,loginName);
pstmt.setString(2,loginPwd);
//執行SQL
rs = pstmt.executeQuery();
while (rs.next()){
loginSuccess = true;
}
}catch (SQLException | ClassNotFoundException e){
e.printStackTrace();
}finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return loginSuccess;
}
/**
* 初始化用戶界面
* @return 用戶輸入的用戶名和密碼等登錄信息
*/
private static Map<String, String> initUI() {
Scanner in = new Scanner(System.in);
System.out.println("用戶名:");
String loginName = in.nextLine();
System.out.println("密碼:");
String loginPwd = in.nextLine();
Map<String, String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName", loginName);
userLoginInfo.put("loginPwd", loginPwd);
return userLoginInfo;
}
}
再輸入asd' or '1'='1
已經不起作用了。
解決SQL注入的關鍵是:用戶提供的信息中即使含有SQL語句的關鍵字,但是這些關鍵字並沒有參與編譯,不起作用。