VUE前端實(shí)現(xiàn)token的無(wú)感刷新方式
前言
說(shuō)實(shí)話,這個(gè)其實(shí)沒(méi)啥好講的,要說(shuō)有復(fù)雜度的話,也主要是在后端。
實(shí)現(xiàn)token無(wú)感刷新對(duì)于前端來(lái)說(shuō)是一項(xiàng)十分常用的技術(shù),其本質(zhì)都是為了優(yōu)化用戶體驗(yàn),當(dāng)token過(guò)期時(shí)不需要用戶調(diào)回登錄頁(yè)重新登錄,而是當(dāng)token失效時(shí),進(jìn)行攔截,發(fā)送刷新token的請(qǐng)求,獲取最新的token進(jìn)行覆蓋,讓用戶感受不到token已過(guò)期。
token刷新的方案
方案一:后端返回過(guò)期時(shí)間,前端判斷token過(guò)期時(shí)間,去調(diào)用刷新token的接口
缺點(diǎn):需要后端提供一個(gè)token過(guò)期時(shí)間的字段;使用本地時(shí)間判斷,若本地時(shí)間被修改,本地時(shí)間比服務(wù)器時(shí)間慢,攔截會(huì)失敗。
方案二:寫(xiě)個(gè)定時(shí)器,定時(shí)刷新token接口
缺點(diǎn):浪費(fèi)資源,消耗性能,不建議采用
方案三:在響應(yīng)攔截器中攔截,判斷token返回過(guò)期后,調(diào)用刷新token接口(?推薦使用)
具體思路
token失效后接口返回401

有感刷新: 清除token,強(qiáng)制跳轉(zhuǎn)回登錄頁(yè),有感知的重新登錄拿到新token替換到本地,體驗(yàn)不好

無(wú)感刷新: 使用登錄時(shí)保存的refresh_token調(diào)用另一個(gè)接口,換回新的token值,替換到本地,再次完成本次未完成的請(qǐng)求(用戶無(wú)感知)
具體步驟:
1、首次登錄的時(shí)候會(huì)獲取到兩個(gè)token, 一個(gè)是平時(shí)請(qǐng)求接口正常使用的token(過(guò)期時(shí)間短),另一個(gè)是專門(mén)用于刷新的refresh_token(過(guò)期時(shí)間一般比較長(zhǎng)),登陸時(shí)都存起來(lái) localStorage.setItem(‘refresh_token’,xxx) localStorage.setItem(‘token’, xxx)
2、在響應(yīng)攔截器中對(duì)401狀態(tài)碼引入刷新token的api方法調(diào)用
3、替換保存本地新的token
4、headers替換新的token
5、axios再次發(fā)起未完成的請(qǐng)求,返回promise對(duì)象到最開(kāi)始發(fā)起請(qǐng)求的頁(yè)面
6、如果refresh_token也過(guò)期了,那就判斷是否過(guò)期,過(guò)期了就清除localstorage跳轉(zhuǎn)回登錄頁(yè)面
登陸時(shí)拿到的后端數(shù)據(jù):

存起來(lái)

refreshToken.js
import request from './request
export function refreshToken() {
const resp = request.get('/refresh_token', {
headers: {
token: `${refresh_token}`
},
__isRefreshToken: true
})
// return resp.code === 0 // 等于0表示刷新token成功
}
export function isRefreshRequest(config) {
return !!config.__isRefreshToken //兩個(gè)取反,變成boolean
}
request.js
import axios from 'axios'
import { refreshToken, isRefreshRequest } form './refreshToken.js'
// 創(chuàng)建axios實(shí)例
const service = axios.create({
// baseURL: '',// 所有的請(qǐng)求地址前綴部分
timeout: 25000, // 請(qǐng)求超時(shí)時(shí)間(毫秒)
withCredentials: true// 異步請(qǐng)求攜帶cookie
})
// 請(qǐng)求攔截器
service.interceptors.request.use((config: any) => {
...
}, error => {
...
})
// 響應(yīng)攔截器
service.interceptors.response.use((response: any) => {
let res = response.data
if (res.code == '401' && !isRefreshRequest(res.config)){ // 如果沒(méi)有權(quán)限且不是刷新token的請(qǐng)求
// 刷新token
try {
const res = await refreshToken()
// 保存新的token
localStorage.setItem('token', res.data.token)
// 有新token后再重新請(qǐng)求
response.config.headers.token = localStorage.getItem('token') // 新token
const resp = await service.request(response.config)
return resp.data
// return service(response.config)
}catch {
localStorage.clear() // 清除token
router.replace('/login') // 跳轉(zhuǎn)到登錄頁(yè)
}
}
}, error => {
...
console.log('error', error)
return Promise.reject(error)
})
問(wèn)題一:如何防止多次刷新token
為了防止多次刷新token,可以通過(guò)一個(gè)變量isRefreshing 去控制是否在刷新token的狀態(tài)
request.js
import axios from 'axios'
import { refreshToken, isRefreshRequest } form './refreshToken.js'
// 創(chuàng)建axios實(shí)例
const service = axios.create({
// baseURL: '',// 所有的請(qǐng)求地址前綴部分
timeout: 25000, // 請(qǐng)求超時(shí)時(shí)間(毫秒)
withCredentials: true// 異步請(qǐng)求攜帶cookie
})
// 請(qǐng)求攔截器
service.interceptors.request.use((config: any) => {
...
}, error => {
...
})
// 響應(yīng)攔截器
service.interceptors.response.use((response: any) => {
let res = response.data
let isRefreshing = false
if (res.code == '401' && ! isRefreshRequest(res.config)){ // 如果沒(méi)有權(quán)限且不是刷新token的請(qǐng)求
if (!isRefreshing) {
isRefreshing = true
// 刷新token
try {
const res = await refreshToken()
// 保存新的token
localStorage.setItem('token', res.data.token)
// 有新token后再重新請(qǐng)求
response.config.headers.token = localStorage.getItem('token') // 新token
const resp = await service.request(response.config)
return resp.data
// return service(response.config)
}catch {
localStorage.clear() // 清除token
router.replace('/login') // 跳轉(zhuǎn)到登錄頁(yè)
}
isRefreshing = false
}
}
}, error => {
...
console.log('error', error)
return Promise.reject(error)
})
問(wèn)題二:同時(shí)發(fā)起兩個(gè)或者兩個(gè)以上的請(qǐng)求時(shí),怎么刷新token
當(dāng)?shù)诙€(gè)過(guò)期的請(qǐng)求進(jìn)來(lái),token正在刷新,我們先將這個(gè)請(qǐng)求存到一個(gè)數(shù)組隊(duì)列中,想辦法讓這個(gè)請(qǐng)求處于等待中,一直等到刷新token后再逐個(gè)重試清空請(qǐng)求隊(duì)列。
那么如何做到讓這個(gè)請(qǐng)求處于等待中呢?
為了解決這個(gè)問(wèn)題,我們得借助Promise。將請(qǐng)求存進(jìn)隊(duì)列中后,同時(shí)返回一個(gè)Promise,讓這個(gè)Promise一直處于Pending狀態(tài)(即不調(diào)用resolve),此時(shí)這個(gè)請(qǐng)求就會(huì)一直等啊等,只要我們不執(zhí)行resolve,這個(gè)請(qǐng)求就會(huì)一直在等待。當(dāng)刷新請(qǐng)求的接口返回來(lái)后,我們?cè)僬{(diào)用resolve,逐個(gè)重試。
request.js
import axios from 'axios'
import { refreshToken, isRefreshRequest } form './refreshToken.js'
// 創(chuàng)建axios實(shí)例
const service = axios.create({
// baseURL: '',// 所有的請(qǐng)求地址前綴部分
timeout: 25000, // 請(qǐng)求超時(shí)時(shí)間(毫秒)
withCredentials: true// 異步請(qǐng)求攜帶cookie
})
// 請(qǐng)求攔截器
service.interceptors.request.use((config: any) => {
...
}, error => {
...
})
// 響應(yīng)攔截器
service.interceptors.response.use((response: any) => {
let res = response.data
let isRefreshing = false
let requests = [] // 請(qǐng)求隊(duì)列
if (res.code == '401' && isRefreshRequest(res.config)){ // 如果沒(méi)有權(quán)限且不是刷新token的請(qǐng)求
if (!isRefreshing) {
isRefreshing = true
// 刷新token
try {
const res = await refreshToken()
// 保存新的token
localStorage.setItem('token', res.data.token)
// 有新token后再重新請(qǐng)求
response.config.headers.token = localStorage.getItem('token') // 新token
// token 刷新后將數(shù)組的方法重新執(zhí)行
requests.forEach((cb) => cb(token))
requests = [] // 重新請(qǐng)求完清空
const resp = await service.request(response.config)
return resp.data
// return service(response.config)
}catch {
localStorage.clear() // 清除token
router.replace('/login') // 跳轉(zhuǎn)到登錄頁(yè)
}
isRefreshing = false
} else {
// 返回未執(zhí)行 resolve 的 Promise
return new Promise(resolve => {
// 用函數(shù)形式將 resolve 存入,等待刷新后再執(zhí)行
request.push(token => {
response.config.headers.token = `${token}`
resolve(service(response.config))
})
})
}
}
}, error => {
...
console.log('error', error)
return Promise.reject(error)
})
具體可以學(xué)習(xí)這個(gè)視頻
token無(wú)感刷新



總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- 如何實(shí)現(xiàn)無(wú)感刷新token
- VUE前端實(shí)現(xiàn)token的無(wú)感刷新3種方案(refresh_token)
- js項(xiàng)目中前端如何實(shí)現(xiàn)無(wú)感刷新token
- 前端雙token無(wú)感刷新圖文詳解
- 前端無(wú)感刷新token的實(shí)現(xiàn)步驟
- Vue項(xiàng)目實(shí)現(xiàn)token無(wú)感刷新的示例代碼
- 雙Token無(wú)感刷新機(jī)制實(shí)現(xiàn)方式
- Vue實(shí)現(xiàn)雙token無(wú)感刷新的示例代碼
- 雙token無(wú)感刷新nodejs+React詳細(xì)解釋(保姆級(jí)教程)
相關(guān)文章
Vue數(shù)據(jù)更新頁(yè)面卻沒(méi)有更新的幾種情況以及解決方法
我們?cè)陂_(kāi)發(fā)過(guò)程中會(huì)碰到數(shù)據(jù)更新,但是頁(yè)面卻沒(méi)有更新的情況,下面這篇文章主要給大家介紹了關(guān)于Vue數(shù)據(jù)更新頁(yè)面卻沒(méi)有更新的幾種情況以及解決方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06
vue cli使用絕對(duì)路徑引用圖片問(wèn)題的解決
這篇文章主要給大家介紹了關(guān)于vue cli使用絕對(duì)路徑引用圖片問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起看看吧。2017-12-12
vue+element-ui中form輸入框無(wú)法輸入問(wèn)題的解決方法
很多初次接觸element-ui的同學(xué),在用到element form組件時(shí)可能會(huì)遇到input框無(wú)法輸入文字的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于vue+element-ui中form輸入框無(wú)法輸入問(wèn)題的解決方法,需要的朋友可以參考下2023-04-04
vue使用echarts實(shí)現(xiàn)中國(guó)地圖和點(diǎn)擊省份進(jìn)行查看功能
這篇文章主要介紹了vue使用echarts實(shí)現(xiàn)中國(guó)地圖和點(diǎn)擊省份進(jìn)行查看功能,本文通過(guò)實(shí)例代碼給大家詳細(xì)講解,對(duì)vue echarts 中國(guó)地圖相關(guān)知識(shí)感興趣的朋友一起看看吧2022-12-12

