| 語法 | 解析階段 | 原理 | SQL 注入風險 | 典型用途 |
#{param} | 預編譯階段 | JDBC PreparedStatement 佔位符 ? | 極低(參數化查詢) | 值參數:WHERE 條件、INSERT 值、UPDATE 值 |
${param} | 動態 SQL 解析階段 | 字串直接替換 | 極高(拼接進 SQL 文本) | 結構元素:表名、欄位名、排序欄位、分庫分表後綴 |
攻擊示例
若傳入 tableName = "user; DROP TABLE user; --",解析後 SQL 變為:
SELECT * FROM user; DROP TABLE user; -- WHERE name = ?
導致惡意刪表執行。
安全實踐:動態表名/欄位名的正確姿勢
1. 必須關閉預編譯
statementType="STATEMENT" 告訴 MyBatis 使用 Statement 而非 PreparedStatement,否則 ${} 替換後的 SQL 仍會被預編譯導致語法錯誤。
2. 白名單驗證(核心防線)
public List
3. Mapper 對應
注意:WHERE 條件值仍用 #{},僅結構部分用經驗證的 ${}。
進階場景:分表查詢
服務層驗證 month 格式為 yyyyMM 且在合法範圍內。
總結原則
- 能用 #{} 絕不使用 ${}。
- 必須用 ${} 時(表名、欄位名、排序欄位),務必在 Service 層做嚴格白名單/正則驗證。
- statementType="STATEMENT" 僅配合 ${} 使用,其餘場景保持預設 PREPARED。
- 日誌記錄完整 SQL(含參數),便於審計追蹤潛在注入嘗試。
- 上一篇文章: Spring 靜態指定實現類策略:註冊表模式與 ApplicationContextAware 完整實作
- 下一篇文章: CentOS 部署 Java Web 環境完整教學:JDK、Tomcat、MySQL 安裝與配置
Copyright ©
程式碼之家 All Rights Reserved