原始代碼問題分析
public boolean checkUser(String tid, String pwd) {
try {
stmt = conn.createStatement();
rs = stmt.executeQuery("Select * from ClientId where ClientId=" + tid + " and where passWord=" + pwd + "");
} catch (SQLException se) {
// 錯誤處理中重複關閉資源,且未返回值 → 編譯錯誤「missing return statement」
}
// 邏輯錯誤:rs.isAfterLast() || rs.isBeforeFirst() 無法正確判斷是否有資料
try {
if (rs.isAfterLast() || rs.isBeforeFirst()) return true;
else return false;
} catch (SQLException se) { }
finally { /* 關閉資源 */ }
}
主要缺陷
- SQL 注入風險:字串拼接未使用 `PreparedStatement`。
- WHERE 子句語法錯誤:`and where` 應為 `and`。
- 結果集判斷錯誤:`isAfterLast/isBeforeFirst` 不可靠,應用 `rs.next()`。
- 資源管理混亂:catch 區塊中關閉資源可能拋出異常導致 finally 失效;且編譯器檢測到某些路徑無 `return`。
- 連線複用問題:方法內關閉 `conn` 會導致後續調用失效。
修正版:使用 PreparedStatement + try-with-resources
public boolean checkUser(String tid, String pwd) {
String sql = "SELECT 1 FROM ClientId WHERE ClientId = ? AND passWord = ?";
try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, tid);
ps.setString(2, pwd);
try (ResultSet rs = ps.executeQuery()) {
return rs.next(); // 存在至少一筆 → true
}
} catch (SQLException e) {
e.printStackTrace(); // 或拋出自定義異常
return false;
}
}
關鍵改進點
- 參數化查詢:防 SQL 注入,自動處理引號轉義。
- try-with-resources:自動關閉 `Connection`、`PreparedStatement`、`ResultSet`,順序正確,無需手動 finally。
- 語義正確:`rs.next()` 同時完成「游標前移」與「是否有資料」判斷。
- 連線獲取內移:方法內建立/關閉連線,避免共享連線關閉導致後續失效;生產環境請改用連線池。
- 明確返回:所有路徑均有 `return boolean`,編譯通過。
進階建議
- 密碼**不可明文儲存**,應儲存雜湊值(BCrypt/Argon2),驗證時比對雜湊。
- 使用連線池(HikariCP、Druid)管理連線生命週期。
- 記錄審計日誌(登入成功/失敗、IP、時間)。