🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

用户登入後,Token 到底該存哪裡?從懵圈到精通的全方位解析

面試官的一個簡單問題,卻讓我陷入了深思。這不僅是前端問題,更是全棧工程師必須掌握的安全基礎。

“說說看,使用者登入後拿到的 Token,你會存在哪裡?”

記得我第一次被問到這個問題時,信心滿滿地回答:“localStorage 呀,簡單方便。”然後,空氣突然安靜了...

有後端小夥伴可能會問,這種前端存儲問題後端也需要關心嗎?答案是:絕對需要! 安全是一個全鏈路的問題,任何一環的疏忽都會導致整個系統的崩潰。

初探:天真的 localStorage 方案

很多前端開發者的第一反應都是 localStorage,因為它確實簡單直觀:

// 登入成功後
localStorage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');

// 請求時自動攜帶
axios.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

優點很明顯:

  • 簡單直觀,上手快速
  • 持久化存儲,頁面刷新不影響使用者體驗
  • API 友好,操作方便

但致命問題在於:

  • 一旦遭遇 XSS 攻擊,攻擊者可以直接透過 JavaScript 讀取你的 Token
  • 相當於把家門鑰匙放在門口的墊子下面
  • 幾乎無法有效防禦 XSS 竊取

深入:真正的解決方案

方案一:HttpOnly Cookie - 傳統的智慧

這是最經典的解決方案,透過服務端設置 HttpOnly 標誌來保護 Token:

// 服務端設置 Cookie(Node.js/Express 範例)
res.cookie('token', 'eyJhbGci...', {
  httpOnly: true,      // 禁止 JavaScript 訪問
  secure: process.env.NODE_ENV === 'production', // 僅 HTTPS
  sameSite: 'strict',  // 防禦 CSRF
  maxAge: 24 * 60 * 60 * 1000 // 1天有效期
});

前端無需特殊處理:

// 瀏覽器會自動在每次請求中攜帶 Cookie
// 前端 JavaScript 無法讀取,徹底防禦 XSS

配套的 CSRF 防護方案:

// 方案1:CSRF Token
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken;

// 方案2:雙重提交 Cookie 驗證
// 服務端同時驗證 Cookie 和 Header 中的 Token

適用場景:

  • 傳統多頁面應用
  • SSR 服務端渲染項目
  • 對 SPA 單頁應用也完全可行

方案二:內存存儲 - 極致的安全追求

對於安全性要求極高的場景,內存存儲是最安全的選擇:

let memoryToken = null;

// 登入後存儲
const login = async (credentials) => {
  const response = await axios.post('/api/login', credentials);
  memoryToken = response.data.token;
  return response;
};

// 請求攔截器
axios.interceptors.request.use(config => {
  if (memoryToken) {
    config.headers.Authorization = `Bearer ${memoryToken}`;
  }
  return config;
});

// 登出或頁面關閉時清理
const logout = () => {
  memoryToken = null;
};

優勢:

  • 完全不持久化,免疫 XSS 攻擊
  • 頁面關閉即失效,安全性最高
  • 實現簡單,無需複雜配置

缺點:

  • 頁面刷新就需要重新登入,用戶體驗較差
  • 移動端應用切換時可能丟失狀態

適用場景:

  • 銀行、金融等高安全要求應用
  • 內部管控系統
  • 敏感操作的身份驗證

方案三:現代 SPA 的黃金標準 - 雙 Token 機制

這才是現代 Web 應用在安全與體驗間的完美平衡:

Token 類型 存儲位置 有效期 用途
Access Token 內存 短(15分鐘-2小時) API 呼叫身份驗證
Refresh Token HttpOnly Cookie 長(7天-30天) 刷新 Access Token

實現方案:

// 登入處理
const handleLogin = async (credentials) => {
  const response = await axios.post('/api/login', credentials);
  const { accessToken } = response.data;

  // Access Token 存內存
  setAccessToken(accessToken);
  // Refresh Token 由服務端設置為 HttpOnly Cookie

  return response;
};

// 請求攔截器 - 自動攜帶 Access Token
axios.interceptors.request.use(config => {
  const token = getAccessToken();
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 響應攔截器 - 自動刷新 Token
axios.interceptors.response.use(
  response => response,
  async error => {
    if (error.response?.status === 401) {
      // Access Token 過期,嘗試刷新
      try {
        const newToken = await refreshToken();
        setAccessToken(newToken);
        // 重試原始請求
        error.config.headers.Authorization = `Bearer ${newToken}`;
        return axios.request(error.config);
      } catch (refreshError) {
        // 刷新失敗,跳轉登入頁
        logout();
        window.location.href = '/login';
      }
    }
    return Promise.reject(error);
  }
);

刷新 Token 的服務端實現:

app.post('/api/refresh', async (req, res) => {
  const refreshToken = req.cookies.refreshToken;

  if (!refreshToken) {
    return res.status(401).json({ message: '需要刷新 Token' });
  }

  try {
    const decoded = verifyRefreshToken(refreshToken);
    const newAccessToken = generateAccessToken({ userId: decoded.userId });

    res.json({ accessToken: newAccessToken });
  } catch (error) {
    res.clearCookie('refreshToken');
    res.status(401).json({ message: '無效的刷新 Token' });
  }
});

全方位方案對比

存儲方案 安全性 用戶體驗 實現複雜度 適用場景
localStorage ❌ 低 ✅ 好 ✅ 簡單 內部工具、演示項目
HttpOnly Cookie ✅ 高 ✅ 好 ✅ 中等 傳統 Web 應用、SSR
內存存儲 ✅ 極高 ❌ 差 ✅ 簡單 高安全要求系統
雙 Token 機制 ✅ 很高 ✅ 好 ❌ 複雜 現代 SPA 應用

面試官的真正期待

初級回答:

"localStorage,因為簡單方便。"

中級回答:

"用 HttpOnly Cookie,因為能防 XSS,但要配合 CSRF 防護。"

高級回答:

"要看具體場景。如果是內部低風險系統,localStorage 的簡潔性也有價值。如果是傳統 Web 應用,HttpOnly Cookie + CSRF Token 是久經考驗的方案。如果是現代 SPA,我推薦 Access Token + Refresh Token 的組合,在安全和體驗間取得最佳平衡。同時要考慮業務的安全要求、用戶的使用習慣和技術團隊的維護能力。"

這才是面試官想聽到的:

  • 理解不同方案的權衡取捨
  • 能夠根據業務場景做出合理選擇
  • 清楚每種方案的安全邊界和風險點
  • 具備全鏈路的安全思維

安全的核心是平衡,不是絕對

回頭看我當初那個 naive 的 "localStorage" 回答,問題不在於技術本身,而在於思考方式。

真正的安全專家不是追求絕對安全,而是懂得:

  • 在什麼業務場景下選擇什麼技術方案
  • 每種方案的風險邊界和應對措施
  • 如何用合適的成本解決合適的風險
  • 如何在安全、體驗、開發效率間找到平衡點

現在當面試官再問我 "Token 該存在哪裡" 時,我會先反問:

"咱們的業務場景是什麼?安全要求級別多高?目標用戶的使用習慣怎樣?技術團隊的維護能力如何?"

因為,沒有最好的方案,只有最合適的方案。安全之路,需要的是持續學習和深度思考。


原文出處:https://juejin.cn/post/7574749664491946018


精選技術文章翻譯,幫助開發者持續吸收新知。
按讚的人:

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬10   ❤️5
424
🥈
我愛JS
📝2   💬8   ❤️4
94
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付