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

前端實(shí)現(xiàn)Token無(wú)感刷新的實(shí)戰(zhàn)指南+源碼

 更新時(shí)間:2025年12月12日 09:45:28   作者:Technical genius  
前端實(shí)現(xiàn)Token無(wú)感刷新主要通過(guò)雙Token機(jī)制(Access Token和Refresh Token)結(jié)合請(qǐng)求攔截器完成,本文將和大家詳細(xì)介紹一下具體的實(shí)現(xiàn)方法,感興趣的小伙伴可以了解下

介紹

前端實(shí)現(xiàn)Token無(wú)感刷新主要通過(guò)雙Token機(jī)制(Access Token和Refresh Token)結(jié)合請(qǐng)求攔截器完成。Access Token用于API訪問(wèn)且生命周期較短(如1-2小時(shí)),Refresh Token用于刷新Access Token且生命周期較長(zhǎng)(如7天)。

實(shí)現(xiàn)原理

在響應(yīng)攔截器中捕獲401狀態(tài)碼,觸發(fā)Refresh Token流程。刷新期間通過(guò)標(biāo)志位(如isRefreshing)防止重復(fù)請(qǐng)求,并將過(guò)期請(qǐng)求暫存隊(duì)列中,待獲取新Token后重新發(fā)送。

核心步驟

Token存儲(chǔ)‌:Access Token建議存內(nèi)存(如Vuex),Refresh Token需存HttpOnly Cookie提升安全性。

攔截器配置‌:

  • 請(qǐng)求攔截器自動(dòng)添加有效Access Token;
  • 響應(yīng)攔截器對(duì)401狀態(tài)碼調(diào)用刷新接口,刷新失敗則跳轉(zhuǎn)登錄頁(yè)。

時(shí)間校驗(yàn)‌:可結(jié)合登錄時(shí)間與當(dāng)前時(shí)間差,在特定區(qū)間(如1-2小時(shí))內(nèi)觸發(fā)刷新。

代碼示例

以下為基于Axios的完整實(shí)現(xiàn):

1.TokenManager.js

class TokenManager {
  constructor() {
    this.isRefreshing = false;
    this.requestsQueue = [];
  }

  // 存儲(chǔ)Token
  setTokens(accessToken, refreshToken) {
    localStorage.setItem('accessToken', accessToken);
    localStorage.setItem('refreshToken', refreshToken);
  }

  getAccessToken() {
    return localStorage.getItem('accessToken');
  }

  getRefreshToken() {
    return localStorage.getItem('refreshToken');
  }

  removeTokens() {
    localStorage.removeItem('accessToken');
    localStorage.removeItem('refreshToken');
  }

  // 刷新Token核心邏輯
  async refreshToken() {
    if (this.isRefreshing) {
      return new Promise((resolve) => {
        this.requestsQueue.push(resolve);
      });
    }
    
    this.isRefreshing = true;
    try {
      const response = await axios.post('/api/auth/refresh', {
        refreshToken: this.getRefreshToken()
      });
      
      const newAccessToken = response.data.accessToken;
      localStorage.setItem('accessToken', newAccessToken);
      
      // 執(zhí)行隊(duì)列中的請(qǐng)求
      this.requestsQueue.forEach(callback => callback(newAccessToken));
      this.requestsQueue = [];
      return newAccessToken;
    } catch (error) {
      this.requestsQueue = [];
      this.removeTokens();
      window.location.href = '/login';
      throw error;
    } finally {
      this.isRefreshing = false;
    }
  }
}

export const tokenManager = new TokenManager();

2.axiosConfig.js

import axios from 'axios';
import { tokenManager } from './tokenManager.js';

// 創(chuàng)建axios實(shí)例
const service = axios.create({
  baseURL: process.env.VITE_API_BASE_URL,
  timeout: 10000
});

// 請(qǐng)求攔截器
service.interceptors.request.use(
  (config) => {
    const token = tokenManager.getAccessToken();
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 響應(yīng)攔截器
service.interceptors.response.use(
  (response) => {
    return response;
  },
  async (error) => {
    const { config, response } = error;
    
    // 處理Token過(guò)期情況
    if (response?.status === 401 && !config._retry) {
      config._retry = true;
      
      try {
        const newToken = await tokenManager.refreshToken();
        config.headers.Authorization = `Bearer ${newToken}`;
        return service(config);
      } catch (refreshError) {
        return Promise.reject(refreshError);
      }
    }
    return Promise.reject(error);
  }
);

export default service;

3.LoginTimeManager.js

export class LoginTimeManager {
  static setLoginTime() {
    localStorage.setItem('loginTime', Date.now().toString());
  }

  static shouldRefreshToken() {
    const loginTime = localStorage.getItem('loginTime');
    if (!loginTime) return false;

    const currentTime = Date.now();
    const elapsed = currentTime - parseInt(loginTime);
    const oneHour = 60 * 60 * 1000;
    const twoHours = 2 * oneHour;

    return elapsed > oneHour && elapsed < twoHours;
  }
}

4.authDemo.vue

<template>
  <div class="auth-container">
    <h2>Token無(wú)感刷新演示</h2>
    <button @click="handleLogin" class="login-btn">模擬登錄</button>
    <button @click="fetchUserData" class="fetch-btn">獲取用戶數(shù)據(jù)</button>
    <div v-if="userData" class="data-display">
      <h3>用戶數(shù)據(jù):</h3>
      <pre>{{ JSON.stringify(userData, null, 2) }}</pre>
    </div>
  </div>
</template>

<script>
import axios from './axiosConfig.js';
import { tokenManager } from './tokenManager.js';
import { LoginTimeManager } from './loginHandler.js';

export default {
  name: 'AuthDemo',
  data() {
    return {
      userData: null
    };
  },
  methods: {
    async handleLogin() {
      try {
        const response = await axios.post('/api/auth/login', {
        username: 'user',
        password: 'pass'
        });
        
        const { accessToken, refreshToken } = response.data;
        tokenManager.setTokens(accessToken, refreshToken);
        LoginTimeManager.setLoginTime();
        alert('登錄成功!');
      } catch (error) {
        alert('登錄失敗!');
      }
    },
    
    async fetchUserData() {
      try {
        const response = await axios.get('/api/user/profile');
        this.userData = response.data;
      } catch (error) {
        console.error('獲取用戶數(shù)據(jù)失敗:', error);
      }
    }
  },
  mounted() {
    // 全局點(diǎn)擊監(jiān)聽(tīng)觸發(fā)Token刷新
    document.addEventListener('click', async () => {
      if (LoginTimeManager.shouldRefreshToken() && !window.isRefreshing) {
        window.isRefreshing = true;
        try {
          await tokenManager.refreshToken();
          LoginTimeManager.setLoginTime();
        } finally {
          window.isRefreshing = false;
        }
      }
    });
  }
};
</script>

<style scoped>
.auth-container {
  padding: 20px;
  max-width: 800px;
  margin: 0 auto;
}

.login-btn, .fetch-btn {
  margin: 10px;
  padding: 10px 20px;
  background: #4CAF50;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

.data-display {
  margin-top: 20px;
  padding: 15px;
  background: #f5f5f5;
  border-radius: 8px;
}
</style>

文件作用

TokenManager.js 的作用

TokenManager.js 負(fù)責(zé)Token的存儲(chǔ)管理和刷新邏輯核心實(shí)現(xiàn)。它通過(guò)雙Token機(jī)制(Access Token和Refresh Token)來(lái)確保認(rèn)證連續(xù)性,當(dāng)Access Token過(guò)期時(shí)自動(dòng)使用Refresh Token獲取新的Access Token。該文件實(shí)現(xiàn)了防止重復(fù)刷新的標(biāo)志位控制(isRefreshing)和過(guò)期請(qǐng)求的隊(duì)列管理(requestsQueue),確保在刷新期間到達(dá)的請(qǐng)求能夠在新Token獲取后重新發(fā)送。同時(shí)提供完整的Token設(shè)置、獲取和清理方法,并在刷新失敗時(shí)自動(dòng)跳轉(zhuǎn)登錄頁(yè)面。

axiosConfig.js 的作用

axiosConfig.js 專門配置Axios的請(qǐng)求和響應(yīng)攔截器。在請(qǐng)求攔截器中自動(dòng)為每個(gè)API請(qǐng)求添加有效的Access Token到Authorization頭部。在響應(yīng)攔截器中捕獲401狀態(tài)碼(Token過(guò)期),并調(diào)用TokenManager的刷新方法。該文件還負(fù)責(zé)在Token刷新成功后,使用新Token重新發(fā)送之前失敗的請(qǐng)求,確保用戶操作不被中斷。

LoginTimeManager.js 的作用

LoginTimeManager.js 提供基于時(shí)間的Token刷新策略。它記錄用戶登錄時(shí)間,并通過(guò)計(jì)算當(dāng)前時(shí)間與登錄時(shí)間的時(shí)間差來(lái)判斷是否應(yīng)該主動(dòng)刷新Token。這種時(shí)間校驗(yàn)機(jī)制可以作為401狀態(tài)碼的補(bǔ)充,在Token即將過(guò)期但尚未過(guò)期時(shí)提前刷新,進(jìn)一步優(yōu)化用戶體驗(yàn)。

authDemo.vue 的作用

authDemo.vue 是前端界面組件,負(fù)責(zé)用戶交互和功能演示。它提供登錄按鈕觸發(fā)認(rèn)證流程,并展示獲取用戶數(shù)據(jù)等受保護(hù)接口的調(diào)用效果。該組件還集成了全局點(diǎn)擊監(jiān)聽(tīng),在特定時(shí)間窗口內(nèi)觸發(fā)Token刷新,展示無(wú)感刷新的實(shí)際運(yùn)行效果。

這四個(gè)文件共同協(xié)作:TokenManager處理核心刷新邏輯,axiosConfig攔截網(wǎng)絡(luò)請(qǐng)求,LoginTimeManager提供時(shí)間策略,authDemo則展示完整功能。這種模塊化設(shè)計(jì)使得代碼結(jié)構(gòu)清晰,易于維護(hù)和擴(kuò)展。

關(guān)鍵優(yōu)化點(diǎn)

  • 防抖刷新‌:通過(guò)標(biāo)志位控制刷新頻率,避免短時(shí)間多次刷新。
  • 錯(cuò)誤處理‌:Refresh Token過(guò)期時(shí)清理本地存儲(chǔ)并跳轉(zhuǎn)登錄。
  • 時(shí)間安全‌:優(yōu)先依賴服務(wù)端401狀態(tài)碼而非本地時(shí)間判斷。

此方案在Token過(guò)期時(shí)自動(dòng)靜默更新,用戶操作不受中斷,顯著提升體驗(yàn)。

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

相關(guān)文章

最新評(píng)論

玛曲县| 尉氏县| 安平县| 昆明市| 汉源县| 天等县| 金湖县| 特克斯县| 上栗县| 库车县| 霍山县| 崇明县| 乌什县| 昌平区| 颍上县| 密山市| 竹北市| 思南县| 葫芦岛市| 渝中区| 道真| 德安县| 屏东县| 丹巴县| 大方县| 安丘市| 梅河口市| 华安县| 大埔区| 额济纳旗| 大关县| 阜平县| 延津县| 杭州市| 博罗县| 嵊泗县| 临泉县| 农安县| 延吉市| 白山市| 灵石县|