作者:ErpanOmer
https://juejin.cn/post/7521936882353471526
如果你做過(guò)任何需要登錄的功能,那么你一定思考過(guò)這個(gè)問(wèn)題:當(dāng)后端甩給我一個(gè)token
時(shí),我一個(gè)前端,到底應(yīng)該把它放在哪兒?
這個(gè)問(wèn)題看似簡(jiǎn)單,無(wú)非就是 LocalStorage
、SessionStorage
、Cookie
三個(gè)選項(xiàng)。但如果我告訴你,一個(gè)錯(cuò)誤的選擇,可能會(huì)直接導(dǎo)致你的網(wǎng)站出現(xiàn)嚴(yán)重的安全漏洞,你是不是會(huì)驚出一身冷汗?
許多開(kāi)發(fā)者(包括曾經(jīng)的我)不假思索地把token
塞進(jìn)LocalStorage
,因?yàn)樗腁PI最簡(jiǎn)單好用。但這種方便的背后,隱藏著巨大的風(fēng)險(xiǎn)。
今天,這篇文章將帶你徹底終結(jié)這個(gè)糾結(jié)。我們將深入對(duì)比這三位“候選人”的優(yōu)劣,剖析它們各自面臨的安全威脅(XSS 和 CSRF),并最終給出一個(gè)當(dāng)前業(yè)界公認(rèn)的最佳實(shí)踐方案。
1. 三種存儲(chǔ)方案對(duì)比
在做決定前,我們先來(lái)快速了解一下這三個(gè)Web存儲(chǔ)方案的基本特性。
| | | |
---|
| | 頁(yè)面會(huì)話期間(標(biāo)簽頁(yè)關(guān)閉即失效) | |
| | | |
| | | 可訪問(wèn)(除非設(shè)置`HttpOnly`) |
| | | **每次HTTP請(qǐng)求都會(huì)自動(dòng)攜帶** |
一目了然,LocalStorage
和SessionStorage
是HTML5提供的新API,更大、更易用。而Cookie
是“老前輩”,小而精,并且有個(gè)獨(dú)一無(wú)二的特性:會(huì)自動(dòng)“粘”在HTTP請(qǐng)求頭里發(fā)給后端。
2. 兩大安全攻擊 XSS 與 CSRF
選擇存儲(chǔ)方案,本質(zhì)上是在權(quán)衡安全和便利。而威脅token
安全的主要是下面兩種。
XSS (跨站腳本攻擊)
- 手法:攻擊者通過(guò)某種方式(比如評(píng)論區(qū))向你的網(wǎng)站注入了惡意的JavaScript腳本。當(dāng)其他用戶訪問(wèn)這個(gè)頁(yè)面時(shí),這段腳本就會(huì)執(zhí)行。
- 目標(biāo):如果你的
token
存在LocalStorage
或SessionStorage
里,那么這段惡意腳本就可以通過(guò)簡(jiǎn)單的localStorage.getItem('token')
輕松地把它偷走,然后發(fā)送到攻擊者的服務(wù)器。token
失竊,你的賬戶就被冒充了。
結(jié)論一:LocalStorage
和 SessionStorage
對(duì) XSS 攻擊是完全不設(shè)防的。只要你的網(wǎng)站存在XSS漏洞,存在里面的任何數(shù)據(jù)都能被輕易竊取。
CSRF (跨站請(qǐng)求偽造)
- 手法:你剛剛登錄了你的銀行網(wǎng)站
bank.com
,你的登錄憑證(Cookie
)被瀏覽器記住了。然后,你沒(méi)有關(guān)閉銀行頁(yè)面,而是點(diǎn)開(kāi)了一個(gè)惡意網(wǎng)站hacker.com
。這個(gè)惡意網(wǎng)站的頁(yè)面里可能有一個(gè)看不見(jiàn)的表單或<img>
標(biāo)簽,它會(huì)自動(dòng)向bank.com/transfer
這個(gè)地址發(fā)起一個(gè)轉(zhuǎn)賬請(qǐng)求。 - 目標(biāo):因?yàn)闉g覽器在發(fā)送請(qǐng)求到
bank.com
時(shí),會(huì)自動(dòng)帶上bank.com
的Cookie
,所以銀行服務(wù)器會(huì)認(rèn)為這個(gè)請(qǐng)求是你本人發(fā)起的,于是轉(zhuǎn)賬就成功了。你神不知鬼不覺(jué)地被“偽造”了意愿。
結(jié)論二:Cookie
如果不加以保護(hù),會(huì)受到 CSRF 攻擊的威脅。
3. 現(xiàn)代Cookie的“優(yōu)勢(shì)”
看到這里你可能會(huì)想:LocalStorage
防不住XSS,Cookie
防不住CSRF,這可怎么辦?
別急,我們的Cookie
經(jīng)過(guò)多年的進(jìn)化,已經(jīng)有了強(qiáng)大的防止手段。
HttpOnly
- 封印JS的訪問(wèn)
如果在設(shè)置Cookie
時(shí),加上HttpOnly
屬性,那么通過(guò)JavaScript(如 document.cookie
)將無(wú)法讀取到這個(gè)Cookie
。
Set-Cookie: token=...; HttpOnly
這意味著,即使網(wǎng)站存在XSS漏洞,攻擊者的惡意腳本也偷不走這個(gè)Cookie
,從根本上阻斷了XSS利用token
的路徑。
SameSite
- 防止攜帶
SameSite
屬性用來(lái)告訴瀏覽器,在跨站請(qǐng)求時(shí),是否應(yīng)該攜帶這個(gè)Cookie
。它有三個(gè)值:
Strict
:最嚴(yán)格。只有當(dāng)請(qǐng)求的發(fā)起方和目標(biāo)網(wǎng)站完全一致時(shí),才會(huì)攜帶Cookie
,能完全防御CSRF。Lax
:比較寬松(現(xiàn)在是大多數(shù)瀏覽器的默認(rèn)值)。允許在“頂級(jí)導(dǎo)航”(如<a>
鏈接、GET表單)的跨站請(qǐng)求中攜帶Cookie
,但在<img>
、<iframe>
、POST表單等“嵌入式”請(qǐng)求中會(huì)攔截。這已經(jīng)能防御大部分CSRF攻擊了。None
:最松。任何情況下都攜帶Cookie
。但必須同時(shí)指定Secure
屬性(即Cookie
只能通過(guò)HTTPS發(fā)送)。
對(duì)于登錄token
,我們通常希望它盡可能安全,所以SameSite=Strict
是最佳選擇。
Secure
- 保證傳輸安全
這個(gè)屬性很簡(jiǎn)單,只要設(shè)置了它,Cookie
就只會(huì)在HTTPS的加密連接中被發(fā)送,可以防止在傳輸過(guò)程中被竊聽(tīng)。
4. 終極答案
綜合以上所有分析,我們終于可以給出當(dāng)前公認(rèn)的最佳、最安全的方案了。
這個(gè)方案的核心是“組合拳”:將不同生命周期的token
存放在不同的地方,各司其職。
我們通常有兩種token
:
- **
AccessToken
**:生命周期很短(如15分鐘),用于訪問(wèn)受保護(hù)的API資源。 - **
RefreshToken
**:生命周期很長(zhǎng)(如7天),專門用來(lái)在AccessToken
過(guò)期后,換取一個(gè)新的AccessToken
。
最佳存儲(chǔ)策略如下:
RefreshToken
: 存放在一個(gè) HttpOnly=true
, Secure=true
, SameSite=Strict
的Cookie
中。
* **為什么?** `RefreshToken`非常關(guān)鍵且長(zhǎng)期有效,所以必須用最安全的方式存儲(chǔ)。`HttpOnly`讓它免受XSS攻擊,`SameSite=Strict`讓它免受CSRF攻擊。前端 JS 完全接觸不到它,只在需要刷新`token`時(shí),由瀏覽器自動(dòng)帶著它去請(qǐng)求`/refresh_token`這個(gè)特定接口。
AccessToken
: 存放在 JavaScript的內(nèi)存中(例如,一個(gè)全局變量、React Context或Vuex/Pinia等狀態(tài)管理庫(kù)里)。
* **為什么?** `AccessToken`需要被JS讀取,并放在HTTP請(qǐng)求的`Authorization`頭里(`Bearer xxx`)發(fā)送給后端。將它放在內(nèi)存中,可以避免XSS直接從`LocalStorage`里掃蕩。當(dāng)用戶關(guān)閉標(biāo)簽頁(yè)或刷新頁(yè)面時(shí),內(nèi)存中的`AccessToken`會(huì)丟失。
- 丟失了怎么辦? 這就是
RefreshToken
發(fā)揮作用的時(shí)候了。當(dāng)應(yīng)用啟動(dòng)或AccessToken
失效時(shí),我們就向后端發(fā)起一個(gè)請(qǐng)求(比如訪問(wèn)/refresh_token
接口),瀏覽器會(huì)自動(dòng)帶上我們安全的RefreshToken
Cookie
,后端驗(yàn)證通過(guò)后,就會(huì)返回一個(gè)新的AccessToken
,我們?cè)侔阉嫒雰?nèi)存。
這個(gè)方案完美地結(jié)合了安全性和可用性,幾乎無(wú)懈可擊。
一張表格說(shuō)透
| | 缺點(diǎn)(安全風(fēng)險(xiǎn)) | |
---|
| | | **不推薦**存儲(chǔ)敏感信息(如Token) |
| API簡(jiǎn)單,標(biāo)簽頁(yè)關(guān)閉即刪 | | |
| | **CSRF** (若無(wú)`SameSite`) | |
**內(nèi)存 + `HttpOnly` Cookie** | **安全** (防XSS+CSRF), **體驗(yàn)好** | | **最佳實(shí)踐** (`AccessToken`存內(nèi)存,`RefreshToken`存`HttpOnly Cookie`) |
希望這篇文章能徹底幫你理清思路。當(dāng)你在實(shí)踐中或者面試被問(wèn)到時(shí),就可以把這套“方案”發(fā)揮出來(lái)。
閱讀原文:原文鏈接
該文章在 2025/7/29 12:35:11 編輯過(guò)