VUE前端實現token的無感刷新方式
前言
說實話,這個其實沒啥好講的,要說有復雜度的話,也主要是在后端。
實現token無感刷新對于前端來說是一項十分常用的技術,其本質都是為了優(yōu)化用戶體驗,當token過期時不需要用戶調回登錄頁重新登錄,而是當token失效時,進行攔截,發(fā)送刷新token的請求,獲取最新的token進行覆蓋,讓用戶感受不到token已過期。
token刷新的方案
方案一:后端返回過期時間,前端判斷token過期時間,去調用刷新token的接口
缺點:需要后端提供一個token過期時間的字段;使用本地時間判斷,若本地時間被修改,本地時間比服務器時間慢,攔截會失敗。
方案二:寫個定時器,定時刷新token接口
缺點:浪費資源,消耗性能,不建議采用
方案三:在響應攔截器中攔截,判斷token返回過期后,調用刷新token接口(?推薦使用)
具體思路
token失效后接口返回401
有感刷新: 清除token,強制跳轉回登錄頁,有感知的重新登錄拿到新token替換到本地,體驗不好
無感刷新: 使用登錄時保存的refresh_token調用另一個接口,換回新的token值,替換到本地,再次完成本次未完成的請求(用戶無感知)
具體步驟:
1、首次登錄的時候會獲取到兩個token, 一個是平時請求接口正常使用的token(過期時間短),另一個是專門用于刷新的refresh_token(過期時間一般比較長),登陸時都存起來 localStorage.setItem(‘refresh_token’,xxx) localStorage.setItem(‘token’, xxx)
2、在響應攔截器中對401狀態(tài)碼引入刷新token的api方法調用
3、替換保存本地新的token
4、headers替換新的token
5、axios再次發(fā)起未完成的請求,返回promise對象到最開始發(fā)起請求的頁面
6、如果refresh_token也過期了,那就判斷是否過期,過期了就清除localstorage跳轉回登錄頁面
登陸時拿到的后端數據:
存起來
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 //兩個取反,變成boolean }
request.js
import axios from 'axios' import { refreshToken, isRefreshRequest } form './refreshToken.js' // 創(chuàng)建axios實例 const service = axios.create({ // baseURL: '',// 所有的請求地址前綴部分 timeout: 25000, // 請求超時時間(毫秒) withCredentials: true// 異步請求攜帶cookie }) // 請求攔截器 service.interceptors.request.use((config: any) => { ... }, error => { ... }) // 響應攔截器 service.interceptors.response.use((response: any) => { let res = response.data if (res.code == '401' && !isRefreshRequest(res.config)){ // 如果沒有權限且不是刷新token的請求 // 刷新token try { const res = await refreshToken() // 保存新的token localStorage.setItem('token', res.data.token) // 有新token后再重新請求 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') // 跳轉到登錄頁 } } }, error => { ... console.log('error', error) return Promise.reject(error) })
問題一:如何防止多次刷新token
為了防止多次刷新token,可以通過一個變量isRefreshing 去控制是否在刷新token的狀態(tài)
request.js
import axios from 'axios' import { refreshToken, isRefreshRequest } form './refreshToken.js' // 創(chuàng)建axios實例 const service = axios.create({ // baseURL: '',// 所有的請求地址前綴部分 timeout: 25000, // 請求超時時間(毫秒) withCredentials: true// 異步請求攜帶cookie }) // 請求攔截器 service.interceptors.request.use((config: any) => { ... }, error => { ... }) // 響應攔截器 service.interceptors.response.use((response: any) => { let res = response.data let isRefreshing = false if (res.code == '401' && ! isRefreshRequest(res.config)){ // 如果沒有權限且不是刷新token的請求 if (!isRefreshing) { isRefreshing = true // 刷新token try { const res = await refreshToken() // 保存新的token localStorage.setItem('token', res.data.token) // 有新token后再重新請求 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') // 跳轉到登錄頁 } isRefreshing = false } } }, error => { ... console.log('error', error) return Promise.reject(error) })
問題二:同時發(fā)起兩個或者兩個以上的請求時,怎么刷新token
當第二個過期的請求進來,token正在刷新,我們先將這個請求存到一個數組隊列中,想辦法讓這個請求處于等待中,一直等到刷新token后再逐個重試清空請求隊列。
那么如何做到讓這個請求處于等待中呢?
為了解決這個問題,我們得借助Promise。將請求存進隊列中后,同時返回一個Promise,讓這個Promise一直處于Pending狀態(tài)(即不調用resolve),此時這個請求就會一直等啊等,只要我們不執(zhí)行resolve,這個請求就會一直在等待。當刷新請求的接口返回來后,我們再調用resolve,逐個重試。
request.js
import axios from 'axios' import { refreshToken, isRefreshRequest } form './refreshToken.js' // 創(chuàng)建axios實例 const service = axios.create({ // baseURL: '',// 所有的請求地址前綴部分 timeout: 25000, // 請求超時時間(毫秒) withCredentials: true// 異步請求攜帶cookie }) // 請求攔截器 service.interceptors.request.use((config: any) => { ... }, error => { ... }) // 響應攔截器 service.interceptors.response.use((response: any) => { let res = response.data let isRefreshing = false let requests = [] // 請求隊列 if (res.code == '401' && isRefreshRequest(res.config)){ // 如果沒有權限且不是刷新token的請求 if (!isRefreshing) { isRefreshing = true // 刷新token try { const res = await refreshToken() // 保存新的token localStorage.setItem('token', res.data.token) // 有新token后再重新請求 response.config.headers.token = localStorage.getItem('token') // 新token // token 刷新后將數組的方法重新執(zhí)行 requests.forEach((cb) => cb(token)) requests = [] // 重新請求完清空 const resp = await service.request(response.config) return resp.data // return service(response.config) }catch { localStorage.clear() // 清除token router.replace('/login') // 跳轉到登錄頁 } isRefreshing = false } else { // 返回未執(zhí)行 resolve 的 Promise return new Promise(resolve => { // 用函數形式將 resolve 存入,等待刷新后再執(zhí)行 request.push(token => { response.config.headers.token = `${token}` resolve(service(response.config)) }) }) } } }, error => { ... console.log('error', error) return Promise.reject(error) })
具體可以學習這個視頻
token無感刷新
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Element Plus 日期選擇器獲取選中的日期格式(當前日期/時間戳格式)
如果想要獲取選中的日期時間就需要通過,Element Plus 日期選擇器?format屬性和value-format屬性,format指定輸入框的格式,value-format?指定綁定值的格式,本篇文章就給大家介紹Element Plus 日期選擇器獲取選中的日期格式(當前日期/時間戳格式),感興趣的朋友一起看看吧2023-10-10el-date-picker日期時間選擇器的選擇時間限制到分鐘級別
文章介紹了如何使用el-date-picker 組件來限制用戶選擇的時間,禁止選擇當前時間的日期及時分,同時允許選擇其他日期的全天時分,通過設置 `pickerOptions` 對象的屬性,可以實現對日期和時間的精確控制,感興趣的朋友跟隨小編一起看看吧2025-01-01vue2.0中vue-cli實現全選、單選計算總價格的實例代碼
本篇文章主要介紹了vue2.0中vue-cli實現全選、單選計算總價格的實例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07移動端滑動切換組件封裝 vue-swiper-router實例詳解
這篇文章主要介紹了移動端滑動切換組件封裝 vue-swiper-router實例詳解,需要的朋友可以參考下2018-11-11Vue-ANTD表單輸入中自定義校驗一些正則表達式規(guī)則介紹
這篇文章主要介紹了Vue-ANTD表單輸入中自定義校驗一些正則表達式規(guī)則介紹,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01