前端實現(xiàn)Token無感刷新的實戰(zhàn)指南+源碼
介紹
前端實現(xiàn)Token無感刷新主要通過雙Token機制(Access Token和Refresh Token)結(jié)合請求攔截器完成。Access Token用于API訪問且生命周期較短(如1-2小時),Refresh Token用于刷新Access Token且生命周期較長(如7天)。
實現(xiàn)原理
在響應攔截器中捕獲401狀態(tài)碼,觸發(fā)Refresh Token流程。刷新期間通過標志位(如isRefreshing)防止重復請求,并將過期請求暫存隊列中,待獲取新Token后重新發(fā)送。
核心步驟
Token存儲:Access Token建議存內(nèi)存(如Vuex),Refresh Token需存HttpOnly Cookie提升安全性。
攔截器配置:
- 請求攔截器自動添加有效Access Token;
- 響應攔截器對401狀態(tài)碼調(diào)用刷新接口,刷新失敗則跳轉(zhuǎn)登錄頁。
時間校驗:可結(jié)合登錄時間與當前時間差,在特定區(qū)間(如1-2小時)內(nèi)觸發(fā)刷新。

代碼示例
以下為基于Axios的完整實現(xiàn):
1.TokenManager.js
class TokenManager {
constructor() {
this.isRefreshing = false;
this.requestsQueue = [];
}
// 存儲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í)行隊列中的請求
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實例
const service = axios.create({
baseURL: process.env.VITE_API_BASE_URL,
timeout: 10000
});
// 請求攔截器
service.interceptors.request.use(
(config) => {
const token = tokenManager.getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 響應攔截器
service.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const { config, response } = error;
// 處理Token過期情況
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無感刷新演示</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() {
// 全局點擊監(jiān)聽觸發(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 負責Token的存儲管理和刷新邏輯核心實現(xiàn)。它通過雙Token機制(Access Token和Refresh Token)來確保認證連續(xù)性,當Access Token過期時自動使用Refresh Token獲取新的Access Token。該文件實現(xiàn)了防止重復刷新的標志位控制(isRefreshing)和過期請求的隊列管理(requestsQueue),確保在刷新期間到達的請求能夠在新Token獲取后重新發(fā)送。同時提供完整的Token設置、獲取和清理方法,并在刷新失敗時自動跳轉(zhuǎn)登錄頁面。
axiosConfig.js 的作用
axiosConfig.js 專門配置Axios的請求和響應攔截器。在請求攔截器中自動為每個API請求添加有效的Access Token到Authorization頭部。在響應攔截器中捕獲401狀態(tài)碼(Token過期),并調(diào)用TokenManager的刷新方法。該文件還負責在Token刷新成功后,使用新Token重新發(fā)送之前失敗的請求,確保用戶操作不被中斷。
LoginTimeManager.js 的作用
LoginTimeManager.js 提供基于時間的Token刷新策略。它記錄用戶登錄時間,并通過計算當前時間與登錄時間的時間差來判斷是否應該主動刷新Token。這種時間校驗機制可以作為401狀態(tài)碼的補充,在Token即將過期但尚未過期時提前刷新,進一步優(yōu)化用戶體驗。
authDemo.vue 的作用
authDemo.vue 是前端界面組件,負責用戶交互和功能演示。它提供登錄按鈕觸發(fā)認證流程,并展示獲取用戶數(shù)據(jù)等受保護接口的調(diào)用效果。該組件還集成了全局點擊監(jiān)聽,在特定時間窗口內(nèi)觸發(fā)Token刷新,展示無感刷新的實際運行效果。
這四個文件共同協(xié)作:TokenManager處理核心刷新邏輯,axiosConfig攔截網(wǎng)絡請求,LoginTimeManager提供時間策略,authDemo則展示完整功能。這種模塊化設計使得代碼結(jié)構(gòu)清晰,易于維護和擴展。
關(guān)鍵優(yōu)化點
- 防抖刷新:通過標志位控制刷新頻率,避免短時間多次刷新。
- 錯誤處理:Refresh Token過期時清理本地存儲并跳轉(zhuǎn)登錄。
- 時間安全:優(yōu)先依賴服務端401狀態(tài)碼而非本地時間判斷。
此方案在Token過期時自動靜默更新,用戶操作不受中斷,顯著提升體驗。

到此這篇關(guān)于前端實現(xiàn)Token無感刷新的實戰(zhàn)指南+源碼的文章就介紹到這了,更多相關(guān)前端無感刷新token內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript遍歷查找數(shù)組中最大值與最小值的方法示例
這篇文章主要介紹了JavaScript遍歷查找數(shù)組中最大值與最小值的方法,結(jié)合實例形式分析了javascript基于數(shù)組遍歷、判斷實現(xiàn)最大值與最小值計算的相關(guān)操作技巧,需要的朋友可以參考下2019-05-05
js動態(tài)創(chuàng)建上傳表單通過iframe模擬Ajax實現(xiàn)無刷新
這篇文章主要介紹了js動態(tài)創(chuàng)建上傳表單通過iframe模擬Ajax無刷新的具體實現(xiàn),需要的朋友可以參考下2014-02-02
細數(shù)promise與async/await的使用及區(qū)別說明
這篇文章主要介紹了細數(shù)promise與async/await的使用及區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
JavaScript實現(xiàn)斐波那契數(shù)列的多種方法(全網(wǎng)最全)
斐波那契數(shù)列是計算機科學和數(shù)學中的一個經(jīng)典問題,也是面試中經(jīng)常被問到的算法題目,本文將詳細介紹 JavaScript 中實現(xiàn)斐波那契數(shù)列的 8 種不同方法,并對它們的性能特點進行分析比較,需要的朋友可以參考下2025-04-04

