為什么MyBatis中常用#{}而不使用${}
前言:一個(gè)真實(shí)的故事引發(fā)的技術(shù)思考
去年雙十一凌晨2點(diǎn),某公司用戶數(shù)據(jù)被刪,因?yàn)橐粋€(gè)MyBatis的${}使用失誤,導(dǎo)致黑客通過地址欄注入惡意SQL,直接清空了用戶表...
這就是我今天要講的:為什么MyBatis必須用#{},打死都不能用${}! 跟著我左手畫個(gè)SQL,右手寫個(gè)防注入,咱們用3個(gè)真實(shí)案例+6個(gè)代碼演示+1個(gè)完整項(xiàng)目,徹底講透這個(gè)價(jià)值百萬的安全問題!
一、新手村裝備:MyBatis參數(shù)傳遞基礎(chǔ)
1.1 參數(shù)占位符是什么?
在MyBatis的XML映射文件中,我們經(jīng)??吹竭@樣的SQL:
SELECT * FROM users WHERE id = #{userId}這里的#{}就是MyBatis的參數(shù)占位符,相當(dāng)于JDBC中的PreparedStatement的 ‘?’。
1.2 兩種占位符對(duì)比表
| 特性 | #{} | ${} |
|---|---|---|
| 參數(shù)類型 | 任意類型 | 只能字符串 |
| SQL注入 | 安全 | 高危 |
| 預(yù)編譯 | 支持 | 不支持 |
| 參數(shù)替換方式 | 帶引號(hào)替換 | 直接拼接 |
| 使用場(chǎng)景 | 值傳遞 | 動(dòng)態(tài)表/列名 |
二、#{}與${}的"孿生兄弟"之謎
2.1 表面相似的代碼
先看兩個(gè)看似相同的查詢:
<!-- 使用#{} -->
<select id="getUserById" resultType="User">
SELECT * FROM users WHERE id = #{userId}
</select>
<!-- 使用${} -->
<select id="getUserByIdUnsafe" resultType="User">
SELECT * FROM users WHERE id = ${userId}
</select>2.2 實(shí)際執(zhí)行的SQL
假設(shè)傳入userId=1:
#{}生成的SQL:
SELECT * FROM users WHERE id = ?
參數(shù)1會(huì)被預(yù)編譯處理
${}生成的SQL:
SELECT * FROM users WHERE id = 1
直接拼接參數(shù)值
三、SQL注入:程序員的午夜兇鈴(代碼級(jí)演示)
3.1 模擬黑客攻擊場(chǎng)景
假設(shè)有個(gè)登錄查詢:
<!-- 危險(xiǎn)寫法! -->
<select id="login" resultType="User">
SELECT * FROM users
WHERE username = '${username}'
AND password = '${password}'
</select>黑客輸入:
- 用戶名:
admin' -- - 密碼:隨意填
生成的SQL:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = '123456'
效果:成功繞過密碼驗(yàn)證,直接登錄admin賬號(hào)!
3.2 使用#{}的正確姿勢(shì)
<select id="safeLogin" resultType="User">
SELECT * FROM users
WHERE username = #{username}
AND password = #{password}
</select>此時(shí)無論輸入什么,參數(shù)都會(huì)被正確處理,無法注入。
四、執(zhí)行過程剖析:預(yù)編譯 vs 字符串拼接
4.1 MyBatis執(zhí)行流程對(duì)比圖
graph TD
A[接收參數(shù)] --> B{占位符類型}
B -->|#{}| C[生成PreparedStatement]
B -->|${}| D[拼接完整SQL]
C --> E[預(yù)編譯SQL]
E --> F[安全執(zhí)行]
D --> G[創(chuàng)建Statement]
G --> H[風(fēng)險(xiǎn)執(zhí)行]4.2 預(yù)編譯的三大優(yōu)勢(shì)
- 防注入:將參數(shù)視為數(shù)據(jù)而非代碼
- 性能提升:重復(fù)查詢復(fù)用編譯結(jié)果
- 類型安全:自動(dòng)處理數(shù)據(jù)類型轉(zhuǎn)換
五、必須使用${}的三種特殊場(chǎng)景
雖然${}很危險(xiǎn),但在某些場(chǎng)景下不得不使用:
5.1 動(dòng)態(tài)表名查詢
<select id="selectByTable" resultType="map">
SELECT * FROM ${tableName}
WHERE id = #{id}
</select>安全建議:
- 對(duì)tableName進(jìn)行白名單校驗(yàn)
- 使用正則表達(dá)式過濾非法字符
// 校驗(yàn)示例
if (!tableName.matches("[a-zA-Z_0-9]+")) {
throw new IllegalArgumentException("Invalid table name");
}5.2 動(dòng)態(tài)排序字段
ORDER BY ${sortField} #{sortOrder}安全方案:
- 使用枚舉限制可排序字段
- 參數(shù)映射轉(zhuǎn)換
public enum SafeSortField {
CREATE_TIME("create_time"),
VIEW_COUNT("view_count");
private final String columnName;
// ...
}六、綜合實(shí)戰(zhàn):電商訂單查詢系統(tǒng)
6.1 需求分析
- 根據(jù)動(dòng)態(tài)條件查詢訂單
- 支持多字段排序
- 分頁(yè)查詢
6.2 安全實(shí)現(xiàn)方案
<select id="searchOrders" resultType="Order">
SELECT * FROM orders
<where>
<if test="userId != null">
user_id = #{userId}
</if>
<if test="startDate != null">
AND create_time >= #{startDate}
</if>
<!-- 更多條件... -->
</where>
<!-- 安全排序方案 -->
<if test="sortBy != null">
ORDER BY
<choose>
<when test="sortBy == 'TIME'">create_time</when>
<when test="sortBy == 'AMOUNT'">total_amount</when>
<otherwise>id</otherwise>
</choose>
${sortOrder}
</if>
LIMIT #{pageSize} OFFSET #{offset}
</select>七、靈魂拷問:你真的懂了嗎?
- 當(dāng)需要?jiǎng)討B(tài)指定查詢列時(shí),應(yīng)該用哪種占位符?
- 為什么說使用${}就像在SQL中開eval()?
- 如何安全地實(shí)現(xiàn)動(dòng)態(tài)表頭導(dǎo)出功能?
把你的答案寫在評(píng)論區(qū),抽3位同學(xué)送《MyBatis深度解析》電子書!
八、終極總結(jié)(保存這張圖?。?/h2>
pie
title 占位符使用準(zhǔn)則
"#{} 常規(guī)參數(shù)" : 95%
"${} 動(dòng)態(tài)表/列" : 4%
"${} 其他場(chǎng)景" : 1%
pie
title 占位符使用準(zhǔn)則
"#{} 常規(guī)參數(shù)" : 95%
"${} 動(dòng)態(tài)表/列" : 4%
"${} 其他場(chǎng)景" : 1%三條黃金法則:
- 能用#{}堅(jiān)決不用${}
- 必須用${}時(shí)要做好過濾
- 所有用戶輸入必須使用#{}處理
九、FAQ精選
Q:為什么框架不直接禁用${}?
A:因?yàn)榇_實(shí)存在需要SQL動(dòng)態(tài)拼接的場(chǎng)景,框架需要保持靈活性,但安全責(zé)任在開發(fā)者。
Q:使用#{}會(huì)導(dǎo)致性能問題嗎?
A:恰恰相反,預(yù)編譯語(yǔ)句通常會(huì)提升性能,因?yàn)榭梢詮?fù)用執(zhí)行計(jì)劃。
Q:如何全局防止濫用?
A:可以通過自定義MyBatis插件,在發(fā)現(xiàn)${}時(shí)發(fā)出警告(需要團(tuán)隊(duì)規(guī)范配合)。
附錄:安全自查清單
- 項(xiàng)目中沒有直接使用${}接收用戶輸入的案例
- 所有動(dòng)態(tài)表/列操作都有白名單校驗(yàn)
- 關(guān)鍵SQL語(yǔ)句都經(jīng)過安全審核
- 定期進(jìn)行SQL注入漏洞掃描
到此這篇關(guān)于為什么MyBatis中常用#{}而不使用${}的文章就介紹到這了,更多相關(guān)MyBatis 常用#{}內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java?Git?Commit?Message使用規(guī)范
這篇文章主要介紹了Java?Git?Commit?Message使用規(guī)范,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助2022-08-08
如何解決創(chuàng)建maven工程時(shí),產(chǎn)生“找不到插件的錯(cuò)誤”問題
這篇文章主要介紹了如何解決創(chuàng)建maven工程時(shí),產(chǎn)生“找不到插件的錯(cuò)誤”問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
java代碼獲取數(shù)據(jù)庫(kù)表里數(shù)據(jù)的總數(shù)操作
這篇文章主要介紹了java代碼獲取數(shù)據(jù)庫(kù)表里數(shù)據(jù)的總數(shù)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08
java如何判斷一個(gè)對(duì)象是否為空對(duì)象
本文主要介紹了java如何判斷一個(gè)對(duì)象是否為空對(duì)象,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

