Vue中如何處理token過期問題
后端為了安全,token一般存在有效時間,當(dāng)token過期,所有請求失效
解決方案
方案一
在請求發(fā)起前攔截每個請求,判斷token的有效時間是否已經(jīng)過期,若已過期,則將請求掛起,先刷新token后再繼續(xù)請求。
- 優(yōu)點: 在請求前攔截,能節(jié)省請求,省流量
- 缺點: 需要后端額外提供一個token過期時間的字段;使用了本地時間判斷,若本地時間被篡改,特別是本地時間比服務(wù)器時間慢時,攔截會失敗
- 使用方法:axios.interceptors.request.use() 這個請求前攔截方法
方案二
根據(jù)攔截返回后的數(shù)據(jù)判斷,若token過期,先刷新token,再進行一次請求。
- 優(yōu)點:不需額外的token過期字段,不需判斷時間
- 缺點: 會消耗多一次請求,耗流量
- 使用方法:axios.interceptors.response.use() 這個響應(yīng)攔截方法
最簡單方法:獲取到過期code,直接跳到登錄頁
方案三:封裝axios基本結(jié)構(gòu)
1、token是存在localStorage中
//在request.js ? import axios from 'axios' ? // 創(chuàng)建一個實例 const service = axios.create({ ? ? baseURL: process.env.VUE_APP_BASE_API,? ? ? timeout: 5000 // request timeout }) ? // 從localStorage中獲取token function getLocalToken () { ? ? const token = window.localStorage.getItem('token') ? ? return token } ? // 給實例添加一個setToken方法,用于登錄后將最新token動態(tài)添加到header,同時將token保存在localStorage中 service.setToken = (token) => { ? instance.defaults.headers['X-Token'] = token ? window.localStorage.setItem('token', token) } ? // 攔截返回的數(shù)據(jù) service.interceptors.response.use(response => { ? // 接下來會在這里進行token過期的邏輯處理 ? return response }, error => { ? return Promise.reject(error) }) ? //暴露 export default service
假如后端接口token過期返回的code是401
//獲取新的token請求 function refreshToken () { ? ? return service.post('/refreshtoken').then(res => res.data) } ? // 攔截返回的數(shù)據(jù) service.interceptors.response.use(response => { ? // 接下來會在這里進行token過期的邏輯處理 ? const { code } = response.data ? ?? ?----------------------------------------------------------- ? ?? ?// 說明token過期了,獲取新的token ?? ? if (code === 401) { ?? ??? ?return refreshToken().then(res => { ?? ??? ?// 刷新token成功,將最新的token更新到header中,同時保存在localStorage中 ?? ? ? ? ?const { token } = res.data ?? ? ? ? ?service.setToken(token) ?? ? ? ? ? ?? ? ? ? ?// 獲取當(dāng)前失敗的請求 ?? ? ? ? ?const config = response.config ?? ? ? ? ?//重置失敗請求的配置 ?? ? ? ? ?config.headers['X-Token'] = token ?? ? ? ? ?config.baseURL = '' " ?? ? ? ? ?//重試當(dāng)前請求并返回promise ?? ? ? ? ?return service(config) ?? ??? ?}).catch( res=>{ ?? ??? ??? ?//重新請求token失敗,跳轉(zhuǎn)到登錄頁 ?? ??? ??? ?window.location.href = '/login ' ?? ??? ?} ) ?? ? } ?? ? -------------------------------------------------------------- ? return response ? }, error => { ? return Promise.reject(error) })
2、問題和優(yōu)化
如果token失效時,存在多個請求,這就會導(dǎo)致多次執(zhí)行刷新token的接口
在request.js中用一個變量來標(biāo)記當(dāng)前是否正在刷新token的狀態(tài),如果正在刷新則不再調(diào)用刷新token的接口
在request.js
// 是否正在刷新的標(biāo)記 let isRefreshing = false ? ----------------------------------------------------------- ? ?? ?// 說明token過期了,獲取新的token ?? ? if (code === 401) { ?? ? ?? ?//判斷一下狀態(tài) ?? ??? ?if( !isRefreshing ){ ?? ??? ??? ?//修改狀態(tài),進入更新token階段 ?? ??? ??? ?isRefreshing = true ?? ??? ??? ? ?? ??? ??? ?return refreshToken().then(res => { ?? ??? ??? ?// 刷新token成功,將最新的token更新到header中,同時保存在localStorage中 ?? ??? ? ? ? ?const { token } = res.data ?? ??? ? ? ? ?service.setToken(token) ?? ??? ? ? ? ? ?? ??? ? ? ? ?// 獲取當(dāng)前失敗的請求 ?? ??? ? ? ? ?const config = response.config ?? ??? ? ? ? ?//重置失敗請求的配置 ?? ??? ? ? ? ?config.headers['X-Token'] = token ?? ??? ? ? ? ?config.baseURL = '' " ?? ??? ? ? ? ?//重試當(dāng)前請求并返回promise ?? ??? ? ? ? ?return service(config) ?? ??? ??? ?}).catch( res=>{ ?? ??? ??? ??? ?//重新請求token失敗,跳轉(zhuǎn)到登錄頁 ?? ??? ??? ??? ?window.location.href = '/login ' ?? ??? ??? ?} ).finally( ()=>{ ?? ??? ??? ??? ?//完成之后在關(guān)閉狀態(tài) ?? ??? ??? ??? ?isRefreshing = false ?? ??? ??? ?} ) ?? ??? ?} ?? ? }
同時發(fā)起兩個或以上的請求時,其他接口如何重試
兩個接口幾乎同時發(fā)起和返回,第一個接口會進入刷新token后重試的流程,而第二個接口需要先存起來,然后等刷新token后再重試。同樣,如果同時發(fā)起三個請求,此時需要緩存后兩個接口,等刷新token后再重試;
當(dāng)?shù)诙€過期的請求進來,token正在刷新,我們先將這個請求存到一個數(shù)組隊列中,想辦法讓這個請求處于等待中,一直等到刷新token后再逐個重試清空請求隊列。
將請求存進隊列中后,同時返回一個Promise,讓這個Promise一直處于Pending狀態(tài)(即不調(diào)用resolve),此時這個請求就會一直等啊等,只要我們不執(zhí)行resolve,這個請求就會一直在等待。當(dāng)刷新請求的接口返回來后,我們再調(diào)用resolve,逐個重試。
// 是否正在刷新的標(biāo)記 let isRefreshing = false // 重試隊列,每一項將是一個待執(zhí)行的函數(shù)形式 let requests = [] ? ----------------------------------------------------------- ? ?? ?// 說明token過期了,獲取新的token ?? ? if (code === 401) { ?? ? ?? ?const config = response.config ?? ? ?? ?//判斷一下狀態(tài) ?? ??? ?if( !isRefreshing ){ ?? ??? ??? ?//修改狀態(tài),進入更新token階段 ?? ??? ??? ?isRefreshing = true ?? ??? ??? ?// 獲取當(dāng)前的請求 ?? ??? ??? ?return refreshToken().then(res => { ?? ??? ??? ?// 刷新token成功,將最新的token更新到header中,同時保存在localStorage中 ?? ??? ? ? ? ?const { token } = res.data ?? ??? ? ? ? ?service.setToken(token) ?? ??? ? ? ? ? ?? ??? ? ? ? ?//重置失敗請求的配置 ?? ??? ? ? ? ?config.headers['X-Token'] = token ?? ??? ? ? ? ?config.baseURL = '' " ?? ??? ? ? ? ? ?? ??? ? ? ? ?//已經(jīng)刷新了token,將所有隊列中的請求進行重試 ?? ??? ? ? ? ?requests.forEach(cb => cb(token)) ?? ??? ? ? ? ?// 重試完了別忘了清空這個隊列 ?? ??? ? ? ? ?requests = [] ?? ??? ? ? ? ? ?? ??? ? ? ? ?return service(config) ?? ??? ??? ?}).catch( res=>{ ?? ??? ??? ??? ?//重新請求token失敗,跳轉(zhuǎn)到登錄頁 ?? ??? ??? ??? ?window.location.href = '/login ' ?? ??? ??? ?} ).finally( ()=>{ ?? ??? ??? ??? ?//完成之后在關(guān)閉狀態(tài) ?? ??? ??? ??? ?isRefreshing = false ?? ??? ??? ?} ) ?? ??? ?} else{ ?? ??? ??? ? // 正在刷新token,返回一個未執(zhí)行resolve的promise ?? ??? ??? ? return new Promise((resolve) => { ?? ? ? ? ? ? // 將resolve放進隊列,用一個函數(shù)形式來保存,等token刷新后直接執(zhí)行 ?? ??? ? ? ? ? ? requests.push((token) => { ?? ??? ??? ? ? ? ? ? ?config.baseURL = '' ?? ??? ??? ? ? ? ? ? ?config.headers['X-Token'] = token ?? ??? ??? ? ? ? ? ? ?resolve(instance(config)) ?? ??? ? ? ? ? ?? ? }) ?? ? ? ? ?? ? }) ?? ??? ?} ?? ? }
3、完整版
//在request.js ? import axios from 'axios' ? // 是否正在刷新的標(biāo)記 let isRefreshing = false // 重試隊列,每一項將是一個待執(zhí)行的函數(shù)形式 let requests = [] ? // 創(chuàng)建一個實例 const service = axios.create({ ? ? baseURL: process.env.VUE_APP_BASE_API,? ? ? timeout: 5000 // request timeout }) ? // 從localStorage中獲取token function getLocalToken () { ? ? const token = window.localStorage.getItem('token') ? ? return token } ? // 給實例添加一個setToken方法,用于登錄后將最新token動態(tài)添加到header,同時將token保存在localStorage中 service.setToken = (token) => { ? instance.defaults.headers['X-Token'] = token ? window.localStorage.setItem('token', token) } ? //獲取新的token請求 function refreshToken () { ? ? return service.post('/refreshtoken').then(res => res.data) } ? // 攔截返回的數(shù)據(jù) service.interceptors.response.use(response => { ? // 接下來會在這里進行token過期的邏輯處理 ? const { code } = response.data ?? ?----------------------------------------------------------- ? ?? ?// 說明token過期了,獲取新的token ?? ? if (code === 401) { ?? ? ?? ?const config = response.config ?? ? ?? ?//判斷一下狀態(tài) ?? ??? ?if( !isRefreshing ){ ?? ??? ??? ?//修改狀態(tài),進入更新token階段 ?? ??? ??? ?isRefreshing = true ?? ??? ??? ?// 獲取當(dāng)前的請求 ?? ??? ??? ?return refreshToken().then(res => { ?? ??? ??? ?// 刷新token成功,將最新的token更新到header中,同時保存在localStorage中 ?? ??? ? ? ? ?const { token } = res.data ?? ??? ? ? ? ?service.setToken(token) ?? ??? ? ? ? ? ?? ??? ? ? ? ?//重置失敗請求的配置 ?? ??? ? ? ? ?config.headers['X-Token'] = token ?? ??? ? ? ? ?config.baseURL = '' " ?? ??? ? ? ? ? ?? ??? ? ? ? ?//已經(jīng)刷新了token,將所有隊列中的請求進行重試 ?? ??? ? ? ? ?requests.forEach(cb => cb(token)) ?? ??? ? ? ? ?// 重試完了別忘了清空這個隊列 ?? ??? ? ? ? ?requests = [] ?? ??? ? ? ? ? ?? ??? ? ? ? ?return service(config) ?? ??? ??? ?}).catch( res=>{ ?? ??? ??? ??? ?//重新請求token失敗,跳轉(zhuǎn)到登錄頁 ?? ??? ??? ??? ?window.location.href = '/login ' ?? ??? ??? ?} ).finally( ()=>{ ?? ??? ??? ??? ?//完成之后在關(guān)閉狀態(tài) ?? ??? ??? ??? ?isRefreshing = false ?? ??? ??? ?} ) ?? ??? ?} else{ ?? ??? ??? ? // 正在刷新token,返回一個未執(zhí)行resolve的promise ?? ??? ??? ? return new Promise((resolve) => { ?? ? ? ? ? ? // 將resolve放進隊列,用一個函數(shù)形式來保存,等token刷新后直接執(zhí)行 ?? ??? ? ? ? ? ? requests.push((token) => { ?? ??? ??? ? ? ? ? ? ?config.baseURL = '' ?? ??? ??? ? ? ? ? ? ?config.headers['X-Token'] = token ?? ??? ??? ? ? ? ? ? ?resolve(instance(config)) ?? ??? ? ? ? ? ?? ? }) ?? ? ? ? ?? ? }) ?? ??? ?} ?? ? } ?? ? -------------------------------------------------------------- ? return response }, error => { ? return Promise.reject(error) }) //暴露 export default service
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue+Java后端進行調(diào)試時解決跨域問題的方式
今天在開發(fā)中遇到有點小問題,vue+Java后端進行調(diào)試時如何解決跨域問題,下面小編給大家分享解決方法,感興趣的朋友一起看看吧2017-10-10vue2.0s中eventBus實現(xiàn)兄弟組件通信的示例代碼
這篇文章主要介紹了vue2.0s中eventBus實現(xiàn)兄弟組件通信的示例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10el-select 數(shù)據(jù)回顯,只顯示value不顯示lable問題
這篇文章主要介紹了el-select 數(shù)據(jù)回顯,只顯示value不顯示lable問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09vue-router相關(guān)基礎(chǔ)知識及工作原理
這篇文章主要介紹了vue-router相關(guān)基礎(chǔ)知識及單頁面應(yīng)用的工作原理,需要的朋友可以參考下2018-03-03vue調(diào)用swiper插件步驟教程(最易理解且詳細(xì))
有時候我們需要在vue中使用輪播組件,如果是在vue組件中引入第三方組件的話,最好通過npm安裝,從而進行統(tǒng)一安裝包管理,下面這篇文章主要給大家介紹了關(guān)于vue調(diào)用swiper插件的相關(guān)資料,需要的朋友可以參考下2023-04-04vue.js中使用echarts實現(xiàn)數(shù)據(jù)動態(tài)刷新功能
這篇文章主要介紹了vue.js中使用echarts實現(xiàn)數(shù)據(jù)動態(tài)刷新功能,需要的朋友可以參考下2019-04-04