使用 PreparedStatement

使用 PreparedStatement

記得我們在前兩週的範例中,對 DB 進行寫入資料的時候是這樣用的:

Statement stat = conn.createStatement();

stat.executeUpdate(
  "insert into MyGuests (firstname, lastname, email) " +
  "values ('Chen', 'Tom', 'tom@xxx.com');"
);

如果要用變數傳入的話會變成這樣:

Statement stat = conn.createStatement();
String firstName = "Chen";
String lastName = "Andy";
String email = "andy@xxx.com";

stat.executeUpdate(
  "insert into MyGuests (firstname, lastname, email) " +
  "values ('" + firstName +"', '" + lastName + "', '" + email + "');"
);

嘛~ 其實也是可以用啦!

SQL Injection 問題

但是會有被 SQL Injection 的問題!
什麼是 SQL Injection 呢? 簡單來個範例好了!

我們 DB Table 中有這些資料

+----+-----------+----------+----------------------+
| id | firstname | lastname | email                |
+----+-----------+----------+----------------------+
|  1 | Chang     | Wayne    | judysocute@gmail.com |
|  2 | Chang     | Andy     | judysocute@gmail.com |
|  3 | Chen      | Tom      | tom@xxx.com          |
|  4 | Chen      | Andy     | andy@xxx.com         |
|  5 | Chen      | Andy     | andy@xxx.com         |
+----+-----------+----------+----------------------+

我們現在有個 Query 的程式是這樣的

Statement stat = conn.createStatement();
String queryID = "1";
ResultSet resultSet = stat
	.executeQuery(
  		"Select * From MyGuests Where id = '" + queryID + "'"
	);

那出來的結果會像是這樣

================================
1
Chang
Wayne
judysocute@gmail.com
2020-06-07
================================

但是呢!這個時候我們把 queryId 的值改掉
改成這樣:

Statement stat = conn.createStatement();
String queryID = "' or 1 = '1";
ResultSet resultSet = stat
	.executeQuery(
  		"Select * From MyGuests Where id = '" + queryID + "'"
	);

那出來的結果,就變成全部資料了!

#實際執行的 SQL 語法:

Select * From MyGuests Where id = '' or 1 = '1';
================================
1
Chang
Wayne
judysocute@gmail.com
2020-06-07
================================

... 略過 ...

================================
5
Chen
Andy
andy@xxx.com
2020-06-21
================================

( 至於為什麼會這樣就交給各位去研究一下 SQL 語法囉! )

當然!這不一定會發生,但是有這個風險存在我們就不能放任不管

心法:

永遠不要相信使用者的輸入!

使用 PreparedStatement

當我們要使用 PreparedStatement 的時候,我們的 Statement 物件就要改用 ConnectionprepareStatement() 方法來取得

// Statement stat = conn.createStatement();
PreparedStatement stat = conn
	.prepareStatement("Select * From MyGuests Where id = ?");

可以看到在查詢參數部分,我們用 ? 來表示
等需要用到的時候我們再使用相對應 setInt()、setString() 等方法來指定 ? 的真正值

PreparedStatement stat = conn
	.prepareStatement("Select * From MyGuests Where id = ?");

stat.setString(1, "2");
stat.executeQuery();
stat.clearParameters();

這邊 setString() 方法的第一個參數是 ? 的位置,第二個參數是要所代表的

而設定完查詢語法時,只要執行 executeQuery()executeUpdate() 就可以讓 SQL 生效了!

使用完之後,如果要重複使用這個 PreparedStatement 的話,可以使用 clearParameters() 方法將將之前指定的值給清空,就可以繼續使用了!

再來個 insert 的用法吧!

PreparedStatement stat = conn
.prepareStatement(
  "insert into MyGuests (firstname, lastname, email) values (?, ?, ?);"
);

stat.setString(1, "Wang");
stat.setString(2, "Jack");
stat.setString(3, "jack@xxx.com");
stat.executeUpdate();

參數化查詢 Wiki

參數化查詢(parameterized query 或 parameterized statement)是指在設計與資料庫連結並存取資料時,在需要填入數值或資料的地方,使用參數(parameter)來給值,這個方法目前已被視為最有效可預防SQL注入攻擊的攻擊手法的防禦方式。
除了安全因素,相比起拼接字串的SQL語句,參數化的查詢往往有效能優勢。因為參數化的查詢能讓不同的資料通過參數到達資料庫,從而公用同一條SQL語句。大多數資料庫會快取解釋SQL語句產生的位元組碼而省下重複解析的開銷。如果採取拼接字串的SQL語句,則會由於運算元據是SQL語句的一部分而非參數的一部分,而反覆大量解釋SQL語句產生不必要的開銷。

wayne

No Comment

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

文章分類