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

Token已過期,我是如何實現無感刷新Token的?

image.png

我們來想像一個場景:你正在一個電商網站上,精心挑選了半小時的商品,填好了複雜的收貨地址,滿心歡喜地點擊提交訂單 Button。

突然,頁面Duang🎈地一下,跳轉到了登錄頁,並提示你:“登錄狀態已過期,請重新登錄”。

那一刻,你的內心是什麼感受?我想大概率是崩潰的,並且想把這個網站拉進黑名單。

這就是一個典型的、因為Token過期處理不當,而導致的災難級用戶體驗。作為一個負責任的開發者,這是我們絕對不能接受的。

今天就聊聊,我們團隊是如何通過請求攔截隊列控制,來實現無感刷新Token的。讓用戶即使在Token過期的情況下,也能無縫地繼續操作,就好像什麼都沒有發生過一樣。


先講基礎知識

為什麼需要兩個Token?

要實現無感刷新,我們首先需要後端同學的配合,採用雙Token的認證機制。

  1. accessToken: 這是我們每次請求業務接口時,都需要在請求頭裡帶上的令牌。它的特點是生命周期短(比如1小時),因為暴露的風險更高。
  2. refreshToken: 它的唯一作用,就是用來獲取一個新的accessToken。它的特點是生命周期長(比如7天),並且需要被安全地存儲(比如HttpOnly的Cookie裡)。

流程是這樣的:用戶登錄成功後,後端會同時返回accessTokenrefreshToken。前端將accessToken存在內存(或LocalStorage)裡,然後在後續的請求中,通過refreshToken來刷新。

image.png


解決思路,利用axios的請求攔截器

我們整個方案的核心,是利用axios(或其他HTTP請求庫)提供的請求攔截器(Interceptor)。它就像一個哨兵,可以在請求發送前和響應返回後,對請求進行攔截和改造。

我們的目標是:

  1. 響應攔截器裡,捕獲到後端返回的accessToken已過期的錯誤(通常是401狀態碼)。
  2. 當捕獲到這個錯誤時,暫停所有後續的API請求。
  3. 使用refreshToken,悄悄地在後台發起一個獲取新accessToken的請求。
  4. 拿到新的accessToken後,更新我們本地存儲的Token
  5. 最後,把之前失敗的請求和被暫停的請求,用新的Token重新發送出去。

這個過程對用戶來說,是完全透明的。他們最多只會感覺到某一次API請求,比平時慢了一點點。


具體怎麼實現?

下面是我們團隊在項目中,實際使用的axios攔截器偽代碼。

import axios from 'axios';

// 創建一個新的axios實例
const api = axios.create({
  baseURL: '/api',
  timeout: 5000,
});

// ------------------- 請求攔截器 -------------------
api.interceptors.request.use(config => {
  const accessToken = localStorage.getItem('accessToken');
  if (accessToken) {
    config.headers.Authorization = `Bearer ${accessToken}`;
  }
  return config;
}, error => {
  return Promise.reject(error);
});

// ------------------- 響應攔截器 -------------------

// 用於標記是否正在刷新token
let isRefreshing = false;
// 用於存儲因為token過期而被掛起的請求
let requestsQueue = [];

api.interceptors.response.use(
  response => {
    return response;
  },
  async error => {
    const { config, response } = error;

    // 如果返回的HTTP狀態碼是401,說明access_token過期了
    if (response && response.status === 401) {

      // 如果當前沒有在刷新token,那麼我們就去刷新token
      if (!isRefreshing) {
        isRefreshing = true;

        try {
          // 調用刷新token的接口
          const { data } = await axios.post('/refresh-token', {
            refreshToken: localStorage.getItem('refreshToken')
          });
          const newAccessToken = data.accessToken;
          localStorage.setItem('accessToken', newAccessToken);

          // token刷新成功後,重新執行所有被掛起的請求
          requestsQueue.forEach(cb => cb(newAccessToken));
          // 清空隊列
          requestsQueue = [];

          // 把本次失敗的請求也重新執行一次
          config.headers.Authorization = `Bearer ${newAccessToken}`;
          return api(config);

        } catch (refreshError) {
          // 如果刷新token也失敗了,說明refreshToken也過期了
          // 此時只能清空本地存儲,跳轉到登錄頁
          console.error('Refresh token failed:', refreshError);
          localStorage.removeItem('accessToken');
          localStorage.removeItem('refreshToken');
          // window.location.href = '/login';
          return Promise.reject(refreshError);
        } finally {
          isRefreshing = false;
        }
      } else {
        // 如果當前正在刷新token,就把這次失敗的請求,存儲到隊列裡
        // 返回一個pending的Promise,等token刷新後再去執行
        return new Promise((resolve) => {
          requestsQueue.push((newAccessToken) => {
            config.headers.Authorization = `Bearer ${newAccessToken}`;
            resolve(api(config));
          });
        });
      }
    }

    return Promise.reject(error);
  }
);

export default api;

這段代碼的關鍵點,也是面試時最能體現你思考深度的地方:

  1. isRefreshing 狀態鎖
    這是為了解決並發問題。想像一下,如果一個頁面同時發起了3個API請求,而accessToken剛好過期,這3個請求會同時收到401。如果沒有isRefreshing這個鎖,它們會同時去調用/refresh-token接口,發起3次刷新請求,這是完全沒有必要的浪費,甚至可能因為並發問題導致後端邏輯出錯。
    有了這個鎖,只有第一個收到401的請求,會真正去執行刷新邏輯。

  2. requestsQueue 請求隊列
    當第一個請求正在刷新Token時(isRefreshing = true),後面那2個收到401的請求怎麼辦?我們不能直接拋棄它們。正確的做法,是把它們的resolve函數推進一個隊列(requestsQueue)裡,暫時掛起。
    等第一個請求成功拿到新的accessToken後,再遍歷這個隊列,把所有被掛起的請求,用新的Token重新執行一遍。


無感刷新Token這個功能,用戶成功的時候,是感知不到它的存在的。

但恰恰是這種無感的細節,區分出了一個能用的應用和一個好用的應用。

因為一個資深的開發者,他不僅關心功能的實現,更應該關心用戶體驗整個系統的健壯性

希望這一套解決思路,能對你有所幫助🤞😁。


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


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

共有 0 則留言


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