首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

MyBatis 動態指定表名與欄位名:${} 與 #{} 差異與防 SQL 注入實務

     編輯:Java 相關知識

核心認知:#{} vs ${}

攻擊示例


若傳入 tableName = "user; DROP TABLE user; --",解析後 SQL 變為:

SELECT * FROM user; DROP TABLE user; -- WHERE name = ?

導致惡意刪表執行。

安全實踐:動態表名/欄位名的正確姿勢

1. 必須關閉預編譯


statementType="STATEMENT" 告訴 MyBatis 使用 Statement 而非 PreparedStatement,否則 ${} 替換後的 SQL 仍會被預編譯導致語法錯誤。

2. 白名單驗證(核心防線)

public List> queryDynamic(QueryParam param) {
    // 1. 嚴格白名單校驗
    Set allowedTables = Set.of("user", "order", "product");
    Set allowedColumns = Set.of("id", "name", "create_time", "status");
    
    if (!allowedTables.contains(param.getTableName())) {
        throw new IllegalArgumentException("非法表名");
    }
    List cols = Arrays.stream(param.getColumns().split(","))
                              .map(String::trim)
                              .filter(allowedColumns::contains)
                              .collect(Collectors.toList());
    if (cols.isEmpty()) throw new IllegalArgumentException("無合法欄位");
    
    // 2. 組裝安全參數
    param.setSafeColumns(String.join(",", cols)); // 已驗證的欄位列表
    param.setSafeTable(param.getTableName());     // 已驗證的表名
    
    // 3. 執行查詢
    return mapper.queryDynamic(param);
}

3. Mapper 對應


注意:WHERE 條件值仍用 #{},僅結構部分用經驗證的 ${}。

進階場景:分表查詢


服務層驗證 month 格式為 yyyyMM 且在合法範圍內。

總結原則

  1. 能用 #{} 絕不使用 ${}。
  2. 必須用 ${} 時(表名、欄位名、排序欄位),務必在 Service 層做嚴格白名單/正則驗證。
  3. statementType="STATEMENT" 僅配合 ${} 使用,其餘場景保持預設 PREPARED。
  4. 日誌記錄完整 SQL(含參數),便於審計追蹤潛在注入嘗試。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
語法解析階段原理SQL 注入風險典型用途
#{param}預編譯階段JDBC PreparedStatement 佔位符 ?極低(參數化查詢)值參數:WHERE 條件、INSERT 值、UPDATE 值
${param}動態 SQL 解析階段字串直接替換極高(拼接進 SQL 文本)結構元素:表名、欄位名、排序欄位、分庫分表後綴