1. 程式人生 > >SQL攻擊-預編譯--緩存

SQL攻擊-預編譯--緩存

可維護性 ins 可讀性 問號 wid 都是 比較 緩存 query

PreparedStatement

l 它是Statement接口的子接口;

l 強大之處:

  • SQL攻擊;
  • 提高代碼的可讀性、可維護性;
  • 提高效率!

l 學習PreparedStatement的用法:

  • 如何得到PreparedStatement對象:

¨ 給出SQL模板!

¨ 調用ConnectionPreparedStatement prepareStatement(String sql模板)

¨ 調用pstmtsetXxx()系列方法sql模板中的?賦值!

¨ 調用pstmtexecuteUpdate()

executeQuery(),但它的方法都沒有參數。

l 預處理的原理

  • 服務器的工作:

¨ 校驗sql語句的語法!

¨ 編譯:一個與函數相似的東西!

¨ 執行:調用函數

  • PreparedStatement

¨ 前提:連接的數據庫必須支持預處理!幾乎沒有不支持的!

¨ 每個pstmt都與一個sql模板綁定在一起,先把sql模板給數據庫,數據庫先進行校驗,再進行編譯。執行時只是把參數傳遞過去而已!

¨ 若二次執行時,就不用再次校驗語法,也不用再次編譯!直接執行!

1 什麽是SQL攻擊

在需要用戶輸入的地方,用戶輸入的是

SQL語句的片段,最終用戶輸入的SQL片段與我們DAO中寫的SQL語句合成一個完整的SQL語句!例如用戶在登錄時輸入的用戶名和密碼都是為SQL語句的片段!

2 演示SQL攻擊

首先我們需要創建一張用戶表,用來存儲用戶的信息。

CREATE TABLE user(

uidCHAR(32) PRIMARY KEY,

usernameVARCHAR(30) UNIQUE KEY NOT NULL,

PASSWORD VARCHAR(30)

);

INSERT INTO user VALUES(‘U_1001‘, ‘zs‘, ‘zs‘);

SELECT * FROM user;

現在用戶表中只有一行記錄,就是zs

下面我們寫一個login()方法!

public void login(String username, String password) {

Connection con = null;

Statement stmt = null;

ResultSet rs = null;

try {

con = JdbcUtils.getConnection();

stmt = con.createStatement();

String sql = "SELECT * FROM user WHERE " +

"username=‘" + username +

"‘ and password=‘" + password + "‘";

rs = stmt.executeQuery(sql);

if(rs.next()) {

System.out.println("歡迎" + rs.getString("username"));

} else {

System.out.println("用戶名或密碼錯誤!");

}

} catch (Exception e) {

throw new RuntimeException(e);

} finally {

JdbcUtils.close(con, stmt, rs);

}

}

下面是調用這個方法的代碼:

login("a‘ or ‘a‘=‘a", "a‘ or ‘a‘=‘a");

這行當前會使我們登錄成功!因為是輸入的用戶名和密碼是SQL語句片段,最終與我們的login()方法中的SQL語句組合在一起!我們來看看組合在一起的SQL語句:

SELECT * FROM tab_user WHERE username=‘a‘ or ‘a‘=‘a‘ and password=‘a‘ or ‘a‘=‘a

3 防止SQL攻擊

l 過濾用戶輸入的數據中是否包含非法字符;

l 分步交驗!先使用用戶名來查詢用戶,如果查找到了,再比較密碼;

l 使用PreparedStatement

4 PreparedStatement是什麽?

PreparedStatement叫預編譯聲明!

PreparedStatementStatement的子接口,你可以使用PreparedStatement來替換Statement

PreparedStatement的好處:

l 防止SQL攻擊;

l 提高代碼的可讀性,以可維護性;

l 提高效率。

5 PreparedStatement的使用

l 使用ConnectionprepareStatement(String sql):即創建它時就讓它與一條SQL模板綁定;

l 調用PreparedStatementsetXXX()系列方法為問號設置值

l 調用executeUpdate()executeQuery()方法,但要註意,調用沒有參數的方法;

String sql = “select * from tab_student where s_number=?”;

PreparedStatement pstmt = con.prepareStatement(sql);

pstmt.setString(1, “S_1001”);

ResultSet rs = pstmt.executeQuery();

rs.close();

pstmt.clearParameters();

pstmt.setString(1, “S_1002”);

rs = pstmt.executeQuery();

在使用Connection創建PreparedStatement對象時需要給出一個SQL模板,所謂SQL模板就是有“?”的SQL語句,其中“?”就是參數。

在得到PreparedStatement對象後,調用它的setXXX()方法為“?”賦值,這樣就可以得到把模板變成一條完整的SQL語句,然後再調用PreparedStatement對象的executeQuery()方法獲取ResultSet對象。

註意PreparedStatement對象獨有的executeQuery()方法是沒有參數的,而StatementexecuteQuery()是需要參數(SQL語句)的。因為在創建PreparedStatement對象時已經讓它與一條SQL模板綁定在一起了,所以在調用它的executeQuery()executeUpdate()方法時就不再需要參數了。

PreparedStatement最大的好處就是在於重復使用同一模板,給予其不同的參數來重復的使用它。這才是真正提高效率的原因。

所以,建議大家在今後的開發中,無論什麽情況,都去需要PreparedStatement,而不是使用Statement

useServerPrepStmts參數

默認使用PreparedStatement是不能執行預編譯的,這需要在url中給出useServerPrepStmts=true參數(MySQL Server 4.1之前的版本是不支持預編譯的,而Connector/J在5.0.5以後的版本,默認是沒有開啟預編譯功能的)。

例如:jdbc:mysql://localhost:3306/test?useServerPrepStmts=true

  這樣才能保證mysql驅動會先把SQL語句發送給服務器進行預編譯,然後在執行executeQuery()時只是把參數發送給服務器。

cachePrepStmts參數

當使用不同的PreparedStatement對象來執行相同的SQL語句時,還是會出現編譯兩次的現象,這是因為驅動沒有緩存編譯後的函數key,導致二次編譯。如果希望緩存編譯後函數的key,那麽就要設置cachePrepStmts參數為true。例如:

jdbc:mysql://localhost:3306/test?useServerPrepStmts=true&cachePrepStmts=true

SQL攻擊-預編譯--緩存