前端實(shí)現(xiàn)Token無(wú)感刷新的實(shí)戰(zhà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)文章
js判斷當(dāng)頁(yè)面無(wú)法回退時(shí)關(guān)閉網(wǎng)頁(yè)否則就history.go(-1)
當(dāng)頁(yè)面沒(méi)有前驅(qū)歷史記錄時(shí),點(diǎn)擊返回按鈕時(shí)直接關(guān)閉頁(yè)面,否則就退回到前一頁(yè)2014-08-08
ES6 let和const定義變量與常量的應(yīng)用實(shí)例分析
這篇文章主要介紹了ES6 let和const定義變量與常量的應(yīng)用,結(jié)合實(shí)例形式分析了ES6使用let定義變量以及使用const定義常量的相關(guān)操作技巧,需要的朋友可以參考下2019-06-06
淺析JavaScript中的特殊函數(shù)及用法小結(jié)
JavaScript中的函數(shù)本質(zhì)上是一個(gè)對(duì)象,我們可以將這個(gè)對(duì)象賦值給一個(gè)變量,這就使JavaScript中的函數(shù)變得非常的靈活,現(xiàn)在就來(lái)淺看一下JavaScript中函數(shù)的一些用法,需要的朋友可以參考下2022-06-06
JS數(shù)組array元素的添加和刪除方法代碼實(shí)例
這篇文章主要介紹了JS數(shù)組array元素的添加和刪除方法代碼實(shí)例,本文直接給出操作代碼實(shí)例,需要的朋友可以參考下2015-06-06
javascript面向?qū)ο笾畉his關(guān)鍵詞用法分析
這篇文章主要介紹了javascript面向?qū)ο笾畉his關(guān)鍵詞用法分析,以實(shí)例形式分析了在不同場(chǎng)合下this使用中的特性及相關(guān)使用技巧,需要的朋友可以參考下2015-01-01
JS實(shí)現(xiàn)網(wǎng)頁(yè)時(shí)鐘特效
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)網(wǎng)頁(yè)時(shí)鐘特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03

