利用Axios實現(xiàn)無感知雙Token刷新的詳細教程
一、引言
在現(xiàn)代系統(tǒng)中,Token認證已成為保障用戶安全的標準做法。然而,盡管許多系統(tǒng)采用了這種認證方式,卻在處理Token刷新方面存在不足,導致用戶體驗不佳。隨著Token有效期的縮短,頻繁的重新登錄成為常見現(xiàn)象,許多系統(tǒng)未能提供一種無縫的、用戶無感知的Token刷新機制。通過結合Vue3和Axios這兩大前端技術棧,我們可以借助Promise機制,開發(fā)出一種更加完善的自動化Token刷新方案,顯著提升系統(tǒng)的穩(wěn)定性和用戶體驗。本文將深入探討這一實現(xiàn)過程,幫助你解決Token刷新難題。
二、示意圖

三、具體實現(xiàn)
了解了基本步驟后,實際的實現(xiàn)過程其實相當簡潔。然而,在具體操作中,仍有許多關鍵細節(jié)需要我們仔細考量,以確保Token刷新機制的穩(wěn)定性和可靠性。
- Token 存儲與管理:首先,明確如何安全地存儲和管理Access Token與Refresh Token。這涉及到瀏覽器的存儲策略,比如使用
localStorage、sessionStorage,存儲策略不在本文中提及,本文采用localStorage 進行存儲。 - 請求攔截器的設置:在Axios中設置請求攔截器,用于在每次發(fā)送請求前檢查Token的有效性。如果發(fā)現(xiàn)Token過期,則觸發(fā)刷新流程。這一步驟需注意避免并發(fā)請求引發(fā)的重復刷新。
- 處理Token刷新的響應邏輯:當Token過期時,通過發(fā)送Refresh Token請求獲取新的Access Token。在這里,需要處理刷新失敗的情況,如Refresh Token也失效時,如何引導用戶重新登錄。
- 隊列機制的引入:在Token刷新過程中,可能會有多個請求被同時發(fā)出。為了避免重復刷新Token,可以引入隊列機制,確保在刷新Token期間,其他請求被掛起,直到新的Token可用。
- 錯誤處理與用戶體驗:最后,要對整個流程中的錯誤進行處理,比如刷新失敗后的重試邏輯、錯誤提示信息等,確保用戶體驗不受影響。
通過以上步驟的實現(xiàn),你可以構建一個用戶無感知、穩(wěn)定可靠的雙Token刷新機制,提升應用的安全性與用戶體驗。接下來,我們將逐一解析這些關鍵步驟的具體實現(xiàn)。
1. 編寫請求攔截器
實現(xiàn)請求攔截器的基本邏輯比較簡單,即在每次請求時自動附帶上Token以進行認證。
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
const userStore = useUserStore()
if (userStore.authInfo.accessToken && userStore.authInfo.accessToken !== "") {
// 設置頭部 token
config.headers.Authorization = RequestConstant.Header.AuthorizationPrefix + userStore.authInfo.accessToken;
}
return config;
}, (error: any) => {
return Promise.reject(error);
}
);
目前的實現(xiàn)方案是,在請求存在有效Token時,將其附帶到請求頭中發(fā)送給服務器。但在一些特殊情況下,某些請求可能不需要攜帶Token。為此,我們可以在請求配置中通過config對象來判斷是否需要攜帶Token。例如:
request: (deptId: number, deptForm: DeptForm): AxiosPromise<void> => {
return request<void>({
url: DeptAPI.UPDATE.endpoint(deptId),
method: "put",
data: deptForm,
headers: {
// 根據(jù)需要添加Token,或者通過自定義邏輯決定是否包含Authorization字段
token: false
}
});
}
那么在請求攔截器中,您需要多加一個判斷,就是判斷請求頭中token是否需要
// 代碼省略
2. 深究響應攔截器
對于雙token刷新的難點就在于響應攔截器中,因為在這里后端會返回token過期的信息。我們需要先清楚后端接口響應內(nèi)容
2.1 接口介紹
- 正常接口響應內(nèi)容
// Status Code: 200 OK
{
"code":"0000",
"msg":"操作成功",
"data":{}
}
- accessToken 過期響應內(nèi)容
// Status Code: 401 Unauthorized
{
"code":"I009",
"msg":"登錄令牌過期"
}
- accessToken 刷新響應內(nèi)容
// Status Code: 200 OK
{
"code": "0000",
"msg": "操作成功",
"data": {
"accessToken": "",
"refreshToken": "",
"expires": ""
}
}
- refreshToken 過期響應內(nèi)容
// Status Code: 200 OK
{
"code": "I009",
"msg": "登錄令牌過期"
}
注意 : 當Status Code不是200時,Axios的響應攔截器會自動進入error方法。在這里,我們可以捕捉到HTTP狀態(tài)碼為401的請求,從而初步判斷請求是由于Unauthorized(未授權)引發(fā)的。然而,觸發(fā)401狀態(tài)碼的原因有很多,不一定都代表Token過期。因此,為了準確判斷Token是否真的過期,我們需要進一步檢查響應體中的code字段。
2.2 響應攔截器編寫
有上面的接口介紹,我們編寫的就簡單,判斷error.response?.status === 401、code === I009 即可,如果出現(xiàn)這種情況就直接刷新token。
service.interceptors.response.use(async (response: AxiosResponse) => {
// 正常請求代碼忽略
return Promise.reject(new Error(msg || "Error"));
},
async (error: any) => {
const userStore = useUserStore()
if (error.response?.status === 401) {
if (error.response?.data?.code === RequestConstant.Code.AUTH_TOKEN_EXPIRED) {
// token 過期處理
// 1. 刷新 token
const loginResult: LoginResult = await userStore.refreshToken()
if (loginResult) {
// refreshToken 未過期
// 2.1 重構請求頭
error.config.headers.Authorization = RequestConstant.Header.AuthorizationPrefix + userStore.authInfo.accessToken;
// 2.2 請求
return await service.request(error.config);
} else {
// refreshToken 過期
// 1. 重置登錄 token , 跳轉(zhuǎn)登錄頁
await userStore.resetToken()
}
} else {
// 如果是系統(tǒng)發(fā)出的401 , 重置登錄 token , 跳轉(zhuǎn)登錄頁
await userStore.resetToken()
}
} else if (error.response?.status === 403) {
// 403 結果處理 , 代碼省略
} else {
// 其他錯誤結果處理 , 代碼省略
}
return Promise.reject(error.message);
}
);
2.3 解決重復刷新問題
編寫完成上面的內(nèi)容,考慮一下多個請求可能同時遇到 Token 過期,如果沒有適當?shù)臋C制控制,這些請求可能會同時發(fā)起刷新 Token 的操作,導致重復請求,甚至可能觸發(fā)后端的安全機制將這些請求標記為危險操作。
為了解決這個問題,我們實現(xiàn)了一個單例 Promise 的刷新邏輯,通過 singletonRefreshToken 確保在同一時間只有一個請求會發(fā)起 Token 刷新操作。其核心思想是讓所有需要刷新的請求共享同一個 Promise,這樣即使有多個請求同時遇到 Token 過期,它們也只會等待同一個刷新操作的結果,而不會導致多次刷新。
/**
* 刷新 token
*/
refreshToken(): Promise<LoginResult> {
// 如果 singletonRefreshToken 不為 null 說明已經(jīng)在刷新中,直接返回
if (singletonRefreshToken !== null) {
return singletonRefreshToken
}
// 設置 singletonRefreshToken 為一個 Promise 對象 , 處理刷新 token 請求
singletonRefreshToken = new Promise<LoginResult>(async (resolve) => {
await AuthAPI.REFRESH.request({
accessToken: this.authInfo.accessToken as string,
refreshToken: this.authInfo.refreshToken as string
}).then(({data}) => {
// 設置刷新后的Token
this.authInfo = data
// 刷新路由
resolve(data)
}).catch(() => {
this.resetToken()
})
})
// 最終將 singletonRefreshToken 設置為 null, 防止 singletonRefreshToken 一直占用
singletonRefreshToken.finally(() => {
singletonRefreshToken = null;
})
return singletonRefreshToken
}
重要點解析:
singletonRefreshToken的使用:singletonRefreshToken是一個全局變量,用于保存當前正在進行的刷新操作。如果某個請求發(fā)現(xiàn)singletonRefreshToken不為null,就說明另一個請求已經(jīng)發(fā)起了刷新操作,它只需等待這個操作完成,而不需要自己再發(fā)起新的刷新請求。
共享同一個
Promise:- 當
singletonRefreshToken被賦值為一個新的Promise時,所有遇到 Token 過期的請求都會返回這個Promise,并等待它的結果。這樣就避免了同時發(fā)起多個刷新請求。
- 當
刷新完成后的處理:
- 刷新操作完成后(無論成功與否),都會通過
finally將singletonRefreshToken置為null,從而確保下一次 Token 過期時能夠重新發(fā)起刷新請求。
- 刷新操作完成后(無論成功與否),都會通過
通過這種機制,我們可以有效地避免重復刷新 Token 的問題,同時也防止了由于過多重復請求而引發(fā)的后端安全性問題。這種方法不僅提高了系統(tǒng)的穩(wěn)定性,還優(yōu)化了資源使用,確保了用戶的請求能夠正確地處理。
四、測試
- 當我們攜帶過期token訪問接口,后端就會返回401狀態(tài)和I009。

這時候進入
const loginResult: LoginResult = await userStore.refreshToken()
- 攜帶之前過期的accessToken和未過期的refreshToken進行刷新
- 攜帶過期的accessToken的原因 :
- 防止未過期的 accessToken 進行刷新
- 防止 accessToken 和 refreshToken 不是同一用戶發(fā)出的
- 其他安全性考慮

- 獲取到正常結果

五、源碼
前端源碼位置 : yf/ yf-vue-admin / src / utils / request.ts
后端源碼位置 : yf/ .. / impl / AuthServiceImpl.java
以上就是利用Axios實現(xiàn)無感知雙Token刷新的詳細教程的詳細內(nèi)容,更多關于Axios無感知Token刷新的資料請關注腳本之家其它相關文章!
相關文章
BootStrap Table復選框默認選中功能的實現(xiàn)代碼(從數(shù)據(jù)庫獲取到對應的狀態(tài)進行判斷是否為選中狀態(tài))
本文通過實例代碼給大家介紹了BootStrap Table復選框默認選中功能(從數(shù)據(jù)庫獲取到對應的狀態(tài)進行判斷是否為選中狀態(tài)),代碼簡單易懂,非常不錯,具有參考借鑒價值,需要的的朋友參考下吧2017-07-07
解決layer彈出層的內(nèi)容頁點擊按鈕跳轉(zhuǎn)到新的頁面問題
今天小編就為大家分享一篇解決layer彈出層的內(nèi)容頁點擊按鈕跳轉(zhuǎn)到新的頁面問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09
JavaScript 開發(fā)工具webstrom使用指南
本文給大家推薦了一款非常熱門的javascript開發(fā)工具webstrom,著重介紹了webstrom的特色功能、設置技巧、使用心得以及快捷鍵匯總,非常的全面。2014-12-12
使用Require.js封裝原生js輪播圖的實現(xiàn)代碼
這篇文章主要介紹了使用Require.js封裝原生js輪播圖的實現(xiàn)代碼,需要的朋友可以參考下2017-06-06

