最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端實(shí)現(xiàn)無(wú)感刷新Token的方法與避坑指南

 更新時(shí)間:2026年03月25日 08:43:40   作者:前端Hardy  
實(shí)現(xiàn)token無(wú)感刷新對(duì)于前端來(lái)說(shuō)是一項(xiàng)非常常用的技術(shù),今天,我們就來(lái)徹底搞懂如何真正實(shí)現(xiàn)無(wú)感刷新Token以及為什么?90%?的實(shí)現(xiàn)都有致命缺陷

刷新 Token 不是“過(guò)期就重新登錄”,而是讓用戶毫無(wú)感知地繼續(xù)使用。

可惜,大多數(shù)項(xiàng)目還在用 401 跳登錄 粗暴處理——這根本不是用戶體驗(yàn),這是放棄治療。

在現(xiàn)代 Web 應(yīng)用中,用戶登錄后通常會(huì)獲得一對(duì) Token:

  • Access Token(短期有效,如 15 分鐘)
  • Refresh Token(長(zhǎng)期有效,如 7 天)

當(dāng) Access Token 過(guò)期時(shí),理想狀態(tài)是:前端自動(dòng)用 Refresh Token 換取新 Token,并重試原請(qǐng)求——整個(gè)過(guò)程用戶無(wú)感,頁(yè)面不跳轉(zhuǎn)、操作不中斷。

但現(xiàn)實(shí)呢?

“Token 過(guò)期 → 彈出登錄框 → 用戶罵一句‘怎么又登出了’ → 關(guān)掉頁(yè)面走人。”

今天,我們就來(lái)徹底搞懂:如何真正實(shí)現(xiàn)“無(wú)感刷新”Token?為什么 90% 的實(shí)現(xiàn)都有致命缺陷?

錯(cuò)誤做法一:在每個(gè)接口里手動(dòng)判斷 401

// 千萬(wàn)別這么寫!
fetch('/api/user')
  .then(res => {
    if (res.status === 401) {
      // 重新登錄 or 刷新 token?
      window.location.href = '/login';
    }
  });

問(wèn)題在哪?

  • 每個(gè)接口都要重復(fù)寫邏輯;
  • 如果多個(gè)請(qǐng)求同時(shí) 401,會(huì)觸發(fā)多次刷新,甚至多次跳登錄;
  • 完全無(wú)法做到“無(wú)感”

錯(cuò)誤做法二:全局?jǐn)r截 401 后直接刷新 Token 并重試一次

這是目前最“主流”的錯(cuò)誤方案:

// 偽代碼:看似聰明,實(shí)則危險(xiǎn)
axios.interceptors.response.use(
  res => res,
  async (error) => {
    if (error.response.status === 401) {
      const newToken = await refreshToken(); // 獲取新 token
      saveToken(newToken);
      
      // 用新 token 重試原請(qǐng)求
      return axios(error.config);
    }
  }
);

表面看沒問(wèn)題,但隱藏三大坑

坑 1:并發(fā)請(qǐng)求雪崩

當(dāng)頁(yè)面剛加載,10 個(gè)接口同時(shí)發(fā)起,而此時(shí) Token 已過(guò)期 ——10 個(gè)請(qǐng)求全部返回 401 → 觸發(fā) 10 次 refreshToken() → 后端收到 10 個(gè)刷新請(qǐng)求!

后果:

  • 后端可能拒絕重復(fù)刷新(安全策略);
  • Refresh Token 被提前消耗,后續(xù)真失效;
  • 用戶反而被踢下線。

坑 2:Refresh Token 泄露風(fēng)險(xiǎn)

如果前端把 Refresh Token 存在 localStorage,一旦 XSS 攻擊成功,攻擊者可長(zhǎng)期盜用賬號(hào)。

安全最佳實(shí)踐:Refresh Token 應(yīng)僅存于 HttpOnly Cookie,前端不可讀!

但上述方案要求前端“拿到新 token”,這就逼你把 Refresh Token 暴露給 JS —— 安全與功能不可兼得?

坑 3:無(wú)限重試死循環(huán)

如果 refreshToken() 本身也返回 401(比如 Refresh Token 也過(guò)期了),重試原請(qǐng)求 → 又 401 → 再刷新 → 再 401 → ……

瀏覽器卡死,內(nèi)存飆升。

正確方式:用“鎖機(jī)制 + 隊(duì)列 + 安全存儲(chǔ)”三位一體

要實(shí)現(xiàn)真正的無(wú)感刷新,必須同時(shí)解決:

  • 并發(fā)控制(只刷一次)
  • 安全存儲(chǔ)(Refresh Token 不暴露給 JS)
  • 失敗兜底(Refresh 失敗時(shí)優(yōu)雅降級(jí))

第一步:后端配合 —— Refresh Token 存 HttpOnly Cookie

HTTP/1.1 200 OK
Set-Cookie: refreshToken=abc123; HttpOnly; Secure; SameSite=Strict; Path=/auth

前端永遠(yuǎn)拿不到 refreshToken,但每次請(qǐng)求會(huì)自動(dòng)攜帶。

第二步:前端實(shí)現(xiàn)“單例刷新鎖 + 請(qǐng)求隊(duì)列”

let isRefreshing = false;
let refreshPromise = null;
const failedQueue = [];

// 重試隊(duì)列中的請(qǐng)求
const processQueue = (error, token = null) => {
  failedQueue.forEach(({ resolve, reject }) => {
    if (error) {
      reject(error);
    } else {
      resolve(token);
    }
  });
  failedQueue.length = 0;
};

axios.interceptors.response.use(
  response => response,
  async (error) => {
    const originalRequest = error.config;

    if (error.response?.status === 401 && !originalRequest._retry) {
      if (isRefreshing) {
        // 已在刷新中,將請(qǐng)求加入隊(duì)列,等待新 token
        return new Promise((resolve, reject) => {
          failedQueue.push({ resolve, reject });
        }).then(token => {
          originalRequest.headers['Authorization'] = `Bearer ${token}`;
          return axios(originalRequest);
        });
      }

      originalRequest._retry = true;
      isRefreshing = true;

      try {
        // 調(diào)用刷新接口(后端從 Cookie 讀 refreshToken)
        const { data } = await axios.post('/auth/refresh');
        const newAccessToken = data.accessToken;

        // 通知所有排隊(duì)的請(qǐng)求
        processQueue(null, newAccessToken);

        // 重試當(dāng)前請(qǐng)求
        originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;
        return axios(originalRequest);
      } catch (refreshError) {
        // 刷新失?。呵蹇毡镜厣矸荩D(zhuǎn)登錄
        clearAuth();
        processQueue(refreshError, null);
        window.location.href = '/login';
        return Promise.reject(refreshError);
      } finally {
        isRefreshing = false;
        refreshPromise = null;
      }
    }

    return Promise.reject(error);
  }
);

關(guān)鍵設(shè)計(jì)解析

機(jī)制作用
isRefreshing 鎖確保同一時(shí)間只發(fā)起一次刷新
failedQueue 隊(duì)列緩存所有因 401 失敗的請(qǐng)求,等新 token 到手后批量重試
_retry 標(biāo)記防止重試后的請(qǐng)求再次進(jìn)入刷新邏輯
HttpOnly Cookie保護(hù) Refresh Token 不被 XSS 竊取

安全補(bǔ)充:前端 Token 存儲(chǔ)建議

Token 類型推薦存儲(chǔ)方式原因
Access Token內(nèi)存(JS 變量)或 sessionStorage短期有效,避免持久化泄露
Refresh TokenHttpOnly Cookie前端不可讀,防 XSS

切勿將任何 Token 存入 localStorage!這是 XSS 攻擊的黃金目標(biāo)。

如何測(cè)試你的刷新邏輯

  • 手動(dòng)將 Access Token 設(shè)為過(guò)期;
  • 快速點(diǎn)擊多個(gè)按鈕,觸發(fā)并發(fā)請(qǐng)求;
  • 觀察 Network 面板:
    • 是否只調(diào)用了一次 /auth/refresh
    • 所有原請(qǐng)求是否最終成功?
  • 模擬 Refresh Token 失效,是否跳轉(zhuǎn)登錄?

結(jié)語(yǔ)

“無(wú)感刷新 Token”不是炫技,而是對(duì)用戶體驗(yàn)和系統(tǒng)安全的基本尊重。那些讓用戶頻繁重新登錄的產(chǎn)品,不是技術(shù)做不到,而是沒把用戶當(dāng)回事

真正的專業(yè),藏在細(xì)節(jié)里:一個(gè)鎖、一個(gè)隊(duì)列、一個(gè) HttpOnly Cookie —— 就是 10% 正確方案 與 90% 錯(cuò)誤實(shí)現(xiàn)的分水嶺。

你的項(xiàng)目還在用“401 就跳登錄”嗎?是時(shí)候升級(jí)了。

歡迎轉(zhuǎn)發(fā)給那個(gè)總說(shuō)“Token 過(guò)期就讓用戶重新登錄”的同事。

到此這篇關(guān)于前端實(shí)現(xiàn)無(wú)感刷新Token的方法與避坑指南的文章就介紹到這了,更多相關(guān)前端無(wú)感刷新token內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 微信小程序的宿主環(huán)境實(shí)現(xiàn)代碼

    微信小程序的宿主環(huán)境實(shí)現(xiàn)代碼

    這篇文章主要介紹了微信小程序的宿主環(huán)境,包括scroll-view 組件的基本使用,text 組件的基本使用及rich-text 組件的基本使用,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • JS實(shí)現(xiàn)選項(xiàng)卡插件的兩種寫法(jQuery和class)

    JS實(shí)現(xiàn)選項(xiàng)卡插件的兩種寫法(jQuery和class)

    這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)選項(xiàng)卡插件的兩種寫法:jQuery和class,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • JavaScript與DropDownList 區(qū)別分析

    JavaScript與DropDownList 區(qū)別分析

    大家都知道,.NET中一些Web服務(wù)器控件解析并編譯,最終被渲染的時(shí)候,其實(shí)是轉(zhuǎn)化成了普通的html控件。
    2010-01-01
  • 使用Webpack壓縮與轉(zhuǎn)譯JavaScript代碼的操作方法

    使用Webpack壓縮與轉(zhuǎn)譯JavaScript代碼的操作方法

    在Web開發(fā)中,代碼的性能和加載時(shí)間是用戶體驗(yàn)的重要組成部分,為此,將JavaScript代碼壓縮和優(yōu)化是發(fā)布前一個(gè)必不可少的步驟,所以本文給大家介紹了如何使用Webpack壓縮與轉(zhuǎn)譯JavaScript代碼,需要的朋友可以參考下
    2024-05-05
  • innerHTML動(dòng)態(tài)添加html代碼和腳本兼容多個(gè)瀏覽器

    innerHTML動(dòng)態(tài)添加html代碼和腳本兼容多個(gè)瀏覽器

    innerHTML動(dòng)態(tài)添加html代碼和腳本,給某個(gè)元素的innerHTML賦值,并使得值中的js代碼有效且兼容多個(gè)瀏覽器,很棒的一個(gè)方法
    2014-10-10
  • 使用html2canvas截圖不全問(wèn)題的解決辦法

    使用html2canvas截圖不全問(wèn)題的解決辦法

    這篇文章主要給大家介紹了關(guān)于使用html2canvas截圖不全問(wèn)題的解決辦法,最近在項(xiàng)目中遇到一個(gè)需求,需要提供網(wǎng)頁(yè)截圖的功能,這里將遇到的問(wèn)題分享給大家,需要的朋友可以參考下
    2023-07-07
  • 前端JavaScript經(jīng)典之Promise詳解

    前端JavaScript經(jīng)典之Promise詳解

    Promise是為了解決回調(diào)地獄問(wèn)題而誕生的,它提供了優(yōu)雅的異步回調(diào)解決方案,這篇文章主要介紹了前端JavaScript經(jīng)典之Promise的相關(guān)資料,需要的朋友可以參考下
    2024-09-09
  • 老生常談JavaScript 數(shù)組方法

    老生常談JavaScript 數(shù)組方法

    本文詳細(xì)介紹了JavaScript數(shù)組方法的分類和具體用法,涵蓋了數(shù)組的添加/刪除、查找/訪問(wèn)、遍歷、排序/反轉(zhuǎn)、轉(zhuǎn)換以及一些其他實(shí)用方法,每種方法都提供了簡(jiǎn)要的解釋和示例代碼,幫助讀者更好地理解和應(yīng)用這些方法,感興趣的朋友一起看看吧
    2025-03-03
  • 詳解js樹形控件—zTree使用總結(jié)

    詳解js樹形控件—zTree使用總結(jié)

    本篇文章主要介紹了js樹形控件—zTree使用總結(jié),樹形控件的使用是應(yīng)用開發(fā)過(guò)程中必不可少的。zTree 是一個(gè)依靠 jQuery 實(shí)現(xiàn)的多功能 “樹插件”。優(yōu)異的性能、靈活的配置、多種功能的組合是 zTree 最大優(yōu)點(diǎn).
    2016-12-12
  • 微信小程序 實(shí)例開發(fā)總結(jié)

    微信小程序 實(shí)例開發(fā)總結(jié)

    這篇文章主要介紹了微信小程序 開發(fā)過(guò)程中遇到問(wèn)題總結(jié)的相關(guān)資料,需要的朋友可以參考下
    2017-04-04

最新評(píng)論

开原市| 林甸县| 土默特右旗| 遵义县| 东辽县| 大足县| 肥西县| 阿拉善盟| 阳信县| 莱西市| 古交市| 新乡县| 章丘市| 崇阳县| 上杭县| 晋中市| 巩留县| 正安县| 佳木斯市| 嘉荫县| 莎车县| 杂多县| 柞水县| 乌鲁木齐市| 安义县| 南皮县| 安图县| 金堂县| 班戈县| 抚顺市| 长海县| 香港 | 曲靖市| 沂源县| 平潭县| 淮滨县| 福建省| 兴和县| 廉江市| 峨边| 汝州市|