MyBatis 防止 SQL 注入的正確用法
一句話(huà)總結(jié)
MyBatis 防 SQL 注入的核心是:參數(shù)一律使用 #{} 做“參數(shù)綁定”(PreparedStatement 的 ? 占位),讓“SQL 結(jié)構(gòu)”和“數(shù)據(jù)”徹底分離;對(duì)不得不用 ${} 的 表名/列名/排序 等 SQL 片段,必須做 白名單 或用 <choose> 枚舉替代;同時(shí)配合代碼規(guī)范、審計(jì)與測(cè)試實(shí)現(xiàn)多層防護(hù)。
1. 先搞清楚:SQL 注入的本質(zhì)是什么?
SQL 注入并不是“沒(méi)轉(zhuǎn)義”,而是:把用戶(hù)輸入拼進(jìn) SQL 語(yǔ)法結(jié)構(gòu)里,導(dǎo)致輸入從“數(shù)據(jù)”變成“指令”。
因此最可靠的防護(hù)手段是:
- 參數(shù)綁定(PreparedStatement):輸入永遠(yuǎn)作為參數(shù)值傳遞,不參與 SQL 語(yǔ)法解析。
2. MyBatis 兩種替換方式:#{}vs${}(核心)
| 寫(xiě)法 | 本質(zhì) | 是否安全 | 適用場(chǎng)景 |
|---|---|---|---|
#{param} | 生成 ? 占位符 + JDBC 參數(shù)綁定(TypeHandler) | 安全 | WHERE/INSERT/UPDATE 的“值” |
${param} | 文本原樣拼接到 SQL(字符串替換) | 高風(fēng)險(xiǎn) | 表名/列名/ORDER BY 等“SQL 片段” |
結(jié)論:
- 能用
#{}就絕不用${}。 ${}只在“無(wú)法用?占位”的場(chǎng)景出現(xiàn),并且必須白名單。
3. 正確用法 1:所有“值”都用#{}(參數(shù)綁定)
3.1 基本示例
<select id="findByName" resultType="User">
SELECT * FROM user WHERE name = #{name}
</select>SQL 形態(tài):
SELECT * FROM user WHERE name = ?
此時(shí)即使輸入是 admin' OR '1'='1,也只會(huì)作為一個(gè)字符串值去匹配,不會(huì)改變 SQL 結(jié)構(gòu)。
3.2 關(guān)鍵點(diǎn)補(bǔ)充:TypeHandler / JDBC 類(lèi)型處理
#{} 會(huì)走 MyBatis 的 TypeHandler,由它決定用 setString/setInt/... 綁定參數(shù)。
- 這不是簡(jiǎn)單“字符串轉(zhuǎn)義”,而是參數(shù)綁定。
- 防注入的關(guān)鍵在于“結(jié)構(gòu)與數(shù)據(jù)分離”。
4. 正確用法 2:動(dòng)態(tài) SQL 標(biāo)簽是安全的(前提是內(nèi)部仍用#{})
MyBatis 的動(dòng)態(tài) SQL(<if> / <where> / <trim> / <choose>)只是動(dòng)態(tài)組裝 SQL 結(jié)構(gòu),并不等于把用戶(hù)輸入拼進(jìn) SQL。
只要條件值仍用 #{},就是安全的:
<select id="query" resultType="User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>5. 常見(jiàn)攻擊面與正確寫(xiě)法
5.1LIKE模糊查詢(xún)
推薦(仍用 #{}):
WHERE name LIKE CONCAT('%', #{name}, '%')不要用:
WHERE name LIKE '%${name}%'注入后果示例:
若用戶(hù)輸入 name 為 ' OR '1'='1,生成的 SQL 將變?yōu)椋?br />WHERE name LIKE '%' OR '1'='1'%'
由于 '1'='1' 恒成立,該查詢(xún)會(huì)繞過(guò)名稱(chēng)過(guò)濾,直接返回全表數(shù)據(jù),導(dǎo)致敏感信息泄露。
5.2IN查詢(xún)(批量參數(shù))
使用 <foreach> 生成多個(gè) #{}:
<select id="selectByIds" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>不要把整段 ids 字符串塞進(jìn) ${}。
風(fēng)險(xiǎn)/后果:
如果把用戶(hù)輸入的 ids 直接拼成 IN (${ids}),攻擊者可構(gòu)造如 1) OR 1=1 --,最終 SQL 可能變?yōu)椋?br />WHERE id IN (1) OR 1=1 --)
從而導(dǎo)致 IN 條件被繞過(guò)、返回全表數(shù)據(jù),甚至在支持多語(yǔ)句的配置下進(jìn)一步造成更嚴(yán)重的破壞。
5.3 分頁(yè)參數(shù)
分頁(yè)參數(shù)也屬于“值”,用 #{}:
SELECT * FROM user LIMIT #{offset}, #{pageSize}6. 不得不用${}的場(chǎng)景:必須白名單/枚舉替代
6.1 為什么這些場(chǎng)景不能用#{}?
JDBC 的 ? 占位符只能替換值,不能替換:
- 表名、列名
ORDER BY的字段ASC/DESC關(guān)鍵字
因此這些場(chǎng)景可能出現(xiàn) ${}。
6.2 推薦做法:用<choose>枚舉排序字段(避免透?jìng)?{sortField})
<select id="findUsers" resultType="User">
SELECT * FROM user
<choose>
<when test="sortField == 'age'">ORDER BY age</when>
<when test="sortField == 'name'">ORDER BY name</when>
<otherwise>ORDER BY id</otherwise>
</choose>
<choose>
<when test="sortOrder == 'DESC'">DESC</when>
<otherwise>ASC</otherwise>
</choose>
</select>6.3 業(yè)務(wù)側(cè)白名單映射(如果必須用${})
- 輸入只允許來(lái)自固定集合(枚舉/Map 映射),嚴(yán)禁直接使用用戶(hù)原始字符串。
- 對(duì)排序字段、表名、列名做正向校驗(yàn)(white list),不要做“黑名單過(guò)濾”。
7. 常見(jiàn)誤區(qū)(糾錯(cuò)點(diǎn))
- 誤區(qū):
#{}是“自動(dòng)轉(zhuǎn)義”所以安全- 更準(zhǔn)確:
#{}是 參數(shù)綁定(結(jié)構(gòu)與數(shù)據(jù)分離),不是靠轉(zhuǎn)義。
- 更準(zhǔn)確:
- 誤區(qū):動(dòng)態(tài) SQL 一定安全
- 動(dòng)態(tài)標(biāo)簽本身安全,但如果你在標(biāo)簽里用
${}拼接用戶(hù)輸入,一樣會(huì)注入。
- 動(dòng)態(tài)標(biāo)簽本身安全,但如果你在標(biāo)簽里用
- 誤區(qū):把
'${name}'包上引號(hào)就安全- 仍是拼接,依然可能被構(gòu)造輸入突破。
8. 多層防護(hù)(生產(chǎn)建議)
僅靠 ORM 習(xí)慣不夠,建議組合:
- 編碼規(guī)范:禁止
${}透?jìng)饔脩?hù)輸入;代碼評(píng)審強(qiáng)制檢查 - 參數(shù)校驗(yàn):對(duì)排序字段、表名等做枚舉/白名單校驗(yàn)
- 審計(jì)與測(cè)試:?jiǎn)螠y(cè)/集成測(cè)試加入注入 payload(如
"' OR '1'='1") - 最小權(quán)限:DB 賬號(hào)權(quán)限最小化(禁止高危權(quán)限)
- 可選:WAF/SQL 防火墻/數(shù)據(jù)庫(kù)審計(jì)(兜底)
9. 面試速答
- Q:MyBatis 如何防 SQL 注入?
- A:對(duì)值使用
#{}走 PreparedStatement 參數(shù)綁定;對(duì)必須拼接的 SQL 片段(表名/列名/排序)使用白名單或<choose>枚舉,禁止${}透?jìng)饔脩?hù)輸入。
- A:對(duì)值使用
- Q:
${}為什么危險(xiǎn)?- A:它是文本替換,輸入會(huì)進(jìn)入 SQL 結(jié)構(gòu),可能變成指令。
- Q:
IN/LIKE怎么寫(xiě)才安全? - A:
IN用<foreach>+#{};LIKE用CONCAT('%', #{x}, '%')。
到此這篇關(guān)于MyBatis 防止 SQL 注入的正確用法的文章就介紹到這了,更多相關(guān)MyBatis 防止 SQL 注入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 開(kāi)啟多線(xiàn)程常見(jiàn)的4種方法
本文主要介紹了Java 開(kāi)啟多線(xiàn)程常見(jiàn)的4種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
SpringBoot集成XXL-JOB實(shí)現(xiàn)靈活控制的分片處理方案
因?yàn)樾枰⑿刑幚硗粡垟?shù)據(jù)表里的數(shù)據(jù),所以比較自然地想到了分片查詢(xún)數(shù)據(jù),可以利用對(duì) id 取模的方法進(jìn)行分片,避免同一條數(shù)據(jù)被重復(fù)處理,所以本文給大家介紹了SpringBoot集成XXL-JOB實(shí)現(xiàn)靈活控制的分片處理方案,需要的朋友可以參考下2024-09-09
編寫(xiě)android撥打電話(huà)apk應(yīng)用實(shí)例代碼
這篇文章主要介紹了編寫(xiě)android撥打電話(huà)apk應(yīng)用實(shí)例代碼,十分的實(shí)用,這里分享給大家,有需要的小伙伴可以參考下2015-04-04
IDEA中創(chuàng)建maven項(xiàng)目webapp目錄無(wú)法識(shí)別即未被標(biāo)識(shí)的解決辦法
在學(xué)習(xí)SpringMVC課程中,基于IDEA新建maven項(xiàng)目模塊后,webapp目錄未被標(biāo)識(shí),即沒(méi)有小藍(lán)點(diǎn)的圖標(biāo)顯示,所以本文給大家介紹了IDEA中創(chuàng)建maven項(xiàng)目webapp目錄無(wú)法識(shí)別即未被標(biāo)識(shí)的解決辦法,需要的朋友可以參考下2024-03-03
使用開(kāi)源項(xiàng)目JAVAE2 進(jìn)行視頻格式轉(zhuǎn)換
這篇文章主要介紹了使用開(kāi)源項(xiàng)目JAVAE 進(jìn)行視頻格式轉(zhuǎn)換,幫助大家更好的利用Java處理視頻,完成自身需求,感興趣的朋友可以了解下2020-11-11
在Mac系統(tǒng)上安裝Java和IntelliJ?IDEA完整筆記
IntelliJ IDEA的智能代碼輔助功能堪稱(chēng)一絕,它具備強(qiáng)大的代碼自動(dòng)補(bǔ)全能力,能依據(jù)上下文精準(zhǔn)推測(cè)開(kāi)發(fā)者的意圖,提供極為準(zhǔn)確的代碼建議,這篇文章主要介紹了在Mac系統(tǒng)上安裝Java和IntelliJ?IDEA的相關(guān)資料,需要的朋友可以參考下2026-02-02
Jmeter命令行執(zhí)行腳本如何設(shè)置動(dòng)態(tài)參數(shù)
這篇文章主要介紹了Jmeter命令行執(zhí)行腳本如何設(shè)置動(dòng)態(tài)參數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
Spring?Cache?集成?Caffeine實(shí)現(xiàn)項(xiàng)目緩存的示例
本文主要介紹了Spring?Cache?集成?Caffeine實(shí)現(xiàn)項(xiàng)目緩存的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12

