前端實(shí)現(xiàn)Token刷新機(jī)制的幾種方法
背景
現(xiàn)在的網(wǎng)站基本會(huì)設(shè)置授權(quán)訪問(wèn),對(duì)于某些資源的訪問(wèn),需要有權(quán)限才能訪問(wèn)或者操作,而服務(wù)端判斷用戶是否有權(quán)訪問(wèn)或者操作,一般是通過(guò)Token實(shí)現(xiàn),而Token一般會(huì)設(shè)置過(guò)期時(shí)間,對(duì)于關(guān)于授權(quán)方面的技術(shù)這里不會(huì)具體描述,這篇主要針對(duì)的是用戶訪問(wèn)授權(quán)資源的時(shí)候,Token過(guò)期了,如何實(shí)現(xiàn)無(wú)需再次登錄,無(wú)痛刷新Token,重新請(qǐng)求。以下是幾種可實(shí)現(xiàn)的方法
在前端實(shí)現(xiàn)刷新 token(access token)的方法有多種,每種方法都有其優(yōu)點(diǎn)和缺點(diǎn)。以下是幾種常見的方法及其評(píng)估:
1. 基于定時(shí)器的自動(dòng)刷新
實(shí)現(xiàn)方式
使用 setTimeout 或 setInterval 定時(shí)在 token 過(guò)期前一定時(shí)間內(nèi)自動(dòng)發(fā)起刷新請(qǐng)求。
let refreshTokenTimeout;
const setRefreshTokenTimer = (expiresIn) => {
// 設(shè)置定時(shí)器,在 token 過(guò)期前 5 分鐘刷新
const refreshTime = expiresIn - 300000; // 5分鐘
refreshTokenTimeout = setTimeout(refreshToken, refreshTime);
}
const refreshToken = async () => {
try {
const response = await fetch('/refresh-token');
const data = await response.json();
// 假設(shè)返回?cái)?shù)據(jù)包含新的 access token 和它的過(guò)期時(shí)間(expiresIn)
localStorage.setItem('refreshToken', data.refreshToken);
setRefreshTokenTimer(data.expiresIn);
} catch (error) {
console.error("Failed to refresh token", error);
// 可以根據(jù)需要處理錯(cuò)誤,例如跳轉(zhuǎn)到登錄頁(yè)面
}
}
// 初次設(shè)置定時(shí)器,這種需要后臺(tái)返回token的過(guò)期時(shí)間,或者用戶登錄的時(shí)候獲取token的時(shí)候同時(shí)存儲(chǔ)當(dāng)前的時(shí)間于localStorage
setRefreshTokenTimer(initialExpiresIn);
優(yōu)點(diǎn)
- 簡(jiǎn)單易實(shí)現(xiàn):邏輯簡(jiǎn)單,易于理解和實(shí)現(xiàn)。
- 自動(dòng)化管理:無(wú)需用戶干預(yù),token 刷新完全自動(dòng)化。
缺點(diǎn)
- 不適用于后臺(tái)刷新:如果用戶離開頁(yè)面或?yàn)g覽器 tab 被掛起,定時(shí)器可能無(wú)法正常工作。
- 資源消耗:可能會(huì)導(dǎo)致不必要的網(wǎng)絡(luò)請(qǐng)求,特別是在用戶不活躍時(shí)。
2. 基于請(qǐng)求攔截器的刷新
實(shí)現(xiàn)方式
在每個(gè) API 請(qǐng)求之前檢查 token 是否即將過(guò)期,如果即將過(guò)期,則首先刷新 token,然后再繼續(xù)發(fā)送請(qǐng)求。
// 使用 Axios 作為示例請(qǐng)求庫(kù)
import axios from 'axios';
const http= axios.create({
baseURL: '/api',
timeout: 10000,
});
http.interceptors.request.use(async (config) => {
const token = localStorage.getItem('refreshToken');
const expiryTime = localStorage.getItem('tokenExpiryTime');
if (expiryTime && Date.now() >= expiryTime - 300000) { // 提前5分鐘刷新
const newTokenData = await refreshToken();
config.headers['Authorization'] = `Bearer ${newTokenData.refreshToken}`;
} else {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
}, (error) => {
return Promise.reject(error);
});
const refreshToken = async () => {
try {
const response = await apiClient.post('/refresh-token');
const data = await response.data;
localStorage.setItem('refreshToken', data.refreshToken);
localStorage.setItem('tokenExpiryTime', Date.now() + data.expiresIn);
return data;
} catch (error) {
console.error("Failed to refresh token", error);
throw error;
}
};
// 在需要時(shí)使用 apiClient 發(fā)出請(qǐng)求
http.get('/some-protected-endpoint').then(response => {
console.log(response.data);
});
優(yōu)點(diǎn)
- 高效:僅在需要時(shí)刷新 token,減少不必要的網(wǎng)絡(luò)請(qǐng)求。
- 適用于后臺(tái)刷新:即使頁(yè)面在后臺(tái)運(yùn)行,也能確保 token 始終有效。
缺點(diǎn)
復(fù)雜度增加:需要處理并發(fā)刷新請(qǐng)求的問(wèn)題,同一時(shí)刻多個(gè)請(qǐng)求可能觸發(fā)多次刷新。
比如,getApi1,getApi2同時(shí)并發(fā)請(qǐng)求,都會(huì)經(jīng)過(guò)Token過(guò)期判斷,都會(huì)發(fā)情token刷新請(qǐng)求,對(duì)于頁(yè)面并發(fā)請(qǐng)求,比如多文件上傳之類的并發(fā),如果并發(fā)6條數(shù)據(jù),則會(huì)請(qǐng)求6次token的刷新。額外延遲:首次請(qǐng)求可能因?yàn)?token 刷新而略有延遲。
3. 基于響應(yīng)攔截器的刷新(最常用)
實(shí)現(xiàn)方式
當(dāng) API 請(qǐng)求返回 401 Unauthorized 錯(cuò)誤時(shí),嘗試刷新 token 并重新發(fā)送原始請(qǐng)求。
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
const http: AxiosInstance = axios.create({
baseURL: '/baseapi', //Mock
timeout: 10000,
});
const openTokenRefresh = false; // 是否開啟token刷新,如果開啟,就設(shè)置為true就好
const pendingRequests: ((token: string) => void)[] = []; // 保存所有請(qǐng)求的回調(diào),用于刷新token后重新請(qǐng)求
const onTokenRefreshed = (token: string):void => {
// 刷新token后,重新請(qǐng)求,
pendingRequests.forEach((callback) => {
callback(token);
});
pendingRequests.length = 0;
};
const addPendingRequest = (callback: (token: string) => void) => {
pendingRequests.push(callback);
};
//是否正在刷新token
let isRefreshing = false;
http.interceptors.response.use((response: AxiosResponse<any>) => response, async (error) => {
if (error.response && error.response.status === 401 && openTokenRefresh) {
//獲取refreshToken
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
//沒有refreshToken,跳轉(zhuǎn)登錄頁(yè)面
window.location.href('/login')
return Promise.reject('登錄失效,請(qǐng)重新登錄');
}
if (!isRefreshing) {
try{
isRefreshing = true;
//請(qǐng)求更新token
const data= await updataToken(refreshToken);
localStorage.setItem('token', data.token);
localStorage.setItem('refreshToken', data.refreshToken);
onTokenRefreshed(data.token);
}catch(err){
window.location.href('/login')
return Promise.reject(err)
}
finally{
isRefreshing = false
}
}
//存儲(chǔ)當(dāng)前請(qǐng)求
return new Promise<AxiosResponse<any>>((resolve) => {
addPendingRequest((newToken: string) => {
response.config.headers['Authorization'] ='Bearer'+ newToken;
resolve(http(response.config));
});
});
}
return Promise.reject(error);
});
優(yōu)點(diǎn)
- 高效:只有在必要時(shí)才刷新 token,避免不必要的請(qǐng)求。
- 靈活性:能夠處理 token 失效后的各種情況,包括并發(fā)問(wèn)題。
缺點(diǎn)
- 復(fù)雜度較高:需要處理并發(fā)刷新、請(qǐng)求隊(duì)列等問(wèn)題,代碼復(fù)雜度高。
- 延遲:第一次請(qǐng)求失敗后需要等待 token 刷新,再次重試,可能會(huì)增加延遲。
總結(jié)
| 方法 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|
| 基于定時(shí)器的自動(dòng)刷新 | 簡(jiǎn)單易實(shí)現(xiàn),自動(dòng)化管理 | 不適用于后臺(tái)刷新,可能導(dǎo)致不必要的網(wǎng)絡(luò)請(qǐng)求 |
| 基于請(qǐng)求攔截器的刷新 | 高效,適用于后臺(tái)刷新,減少不必要的網(wǎng)絡(luò)請(qǐng)求, | 復(fù)雜度增加,需要處理并發(fā)刷新問(wèn)題,同一時(shí)刻多個(gè)請(qǐng)求可能觸發(fā)多次刷新。首次請(qǐng)求可能延遲 |
| 基于響應(yīng)攔截器的刷新 | 只有在必要時(shí)才刷新 token,避免不必要的請(qǐng)求,能夠處理 token 失效后的各種情況,包括并發(fā)問(wèn)題。 | 首次請(qǐng)求失敗可能增加延遲 |
選擇哪種方法取決于你的具體需求和系統(tǒng)架構(gòu),通?;谡?qǐng)求攔截器和響應(yīng)攔截器的方法更為普遍,因?yàn)樗鼈兡軌蚋玫貞?yīng)對(duì)復(fù)雜的實(shí)際場(chǎng)景。
到此這篇關(guān)于前端實(shí)現(xiàn)Token刷新機(jī)制的幾種方法的文章就介紹到這了,更多相關(guān)前端實(shí)現(xiàn)Token刷新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)控制打開文件另存為對(duì)話框的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)控制打開文件另存為對(duì)話框的方法,實(shí)例分析了javascript實(shí)現(xiàn)文件另存為的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
setInterval 不準(zhǔn)的原因及問(wèn)題解決方案
setInterval 是 JavaScript 中用于定時(shí)執(zhí)行任務(wù)的常用方法,本文主要介紹了setInterval 不準(zhǔn)的原因及問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02
javascript筆試題目附答案@20081025_jb51.net
網(wǎng)上找的javascript筆試題目,留檔給自己作參考。2008-10-10
將中國(guó)標(biāo)準(zhǔn)時(shí)間轉(zhuǎn)換成標(biāo)準(zhǔn)格式的代碼
這篇文章主要介紹了將中國(guó)標(biāo)準(zhǔn)時(shí)間轉(zhuǎn)換成標(biāo)準(zhǔn)格式的方法,需要的朋友可以參考下2014-03-03
JavaScript中使用ActiveXObject操作本地文件夾的方法
以前一直用vbscript來(lái)操作文件夾,才發(fā)現(xiàn)原來(lái)使用JavaScript也是可以的,肯定不如vbs用的簡(jiǎn)單,不過(guò)學(xué)習(xí)一下還是不錯(cuò)的2014-03-03
獲取當(dāng)前網(wǎng)頁(yè)document.url location.href區(qū)別總結(jié)
請(qǐng)教:document.URL和window.location.href區(qū)別2008-05-05
鼠標(biāo)劃過(guò)實(shí)現(xiàn)延遲加載并隱藏層的js代碼
鼠標(biāo)劃過(guò)延遲加載隱藏層的效果,想必大家都有見到過(guò)吧,在本文將為大家詳細(xì)介紹下使用js是如何實(shí)現(xiàn)的,感興趣的朋友可以參考下2013-10-10

