? ? ? ? Token在計算機身份認證中是令牌(臨時)的意思,在詞法分析中是標記的意思。一般作為邀請、登錄系統(tǒng)使用。
????????在vue項目中實現(xiàn)Token替換----前端學(xué)海
????????這里使用的vue中的請求攔截器,根據(jù)后端返回的Token的生成時間,以及該Token的有效期和當(dāng)前發(fā)送請求的時間進行計算,判斷當(dāng)前Token是否快要過期,以確定是否開始請求新的Token。
計算公式為:Token有效時間 -(當(dāng)前時間 - Token的生成時間)= 剩余的Token有效時間
? ??????這里我的判斷為,Token剩余有效時間,小于五分鐘的時候,調(diào)用刷新Token的接口,請求最新的Token。
在請求新Token期間,可能同時會有好幾個請求同時發(fā)起,我們在這里將這些請求攔截下來,通過Promise,將這些請求,完整的防在待執(zhí)行的請求隊列中,
當(dāng)新的Token請求成功,更新本地的Token之后,開始執(zhí)行攔截的請求隊列,并將這些請求的header中的Token替換為最新的Token,完成Token整個流程。
如果請求新Token接口失敗,并且原Token已過了有效期時,提示登錄過期,重定向到登錄頁,重新登錄!
????????如有建議,歡迎在評論區(qū)指出?。?!
請看代碼
import axios from 'axios' // 引入axios
import loginApi from './api/article/loginApi'
import router from '../router'
import qs from 'qs'
import {
? Message
} from 'element-ui'
console.info(location.hostname)
if (location.hostname === 'localhost' ||
? location.hostname === '127.0.0.1') {
? axios.defaults.baseURL = '/api/yifd'
} else {
? axios.defaults.baseURL = `https://${location.hostname}/proxy`
}
// eslint-disable-next-line no-unused-vars
/* 是否正在刷新的標志 */
window.isRefreshing = false
// token是否過期,默認為否
window.tokenOverdue = false
/* 存儲請求的數(shù)組 */
const refreshSubscribers = []
/* 將所有的請求都push到數(shù)組中,其實數(shù)組是[function(token){}, function(token){},...] */
function subscribeTokenRefresh (cb) {
? refreshSubscribers.push(cb)
}
/* 數(shù)組中的請求得到新的token之后自執(zhí)行,用新的token去請求數(shù)據(jù) */
function onRrefreshed (token) {
? refreshSubscribers.map(cb => cb(token))
}
function tokenIsOverdue () { // token是否過期
? const time = window.sessionStorage.getItem('tokenTime') // token有效期,秒級,后端返回,瀏覽器存儲的
? const queryTime = parseInt(window.sessionStorage.getItem('tokenQueryTime')) // token獲取的時間,秒級
? const nowTime = new Date().getTime()
? const arealyTime = Math.ceil(nowTime / 1000) - queryTime // 已經(jīng)過去的時間
? // console.log(time, queryTime, Math.ceil(nowTime / 1000), arealyTime)
? if ((time - arealyTime) < 300) { // token五分鐘內(nèi)即將過期時開啟替換token,目前后臺的token有效期為30分鐘。
? ? return true
? } else {
? ? return false
? }
}
tokenIsOverdue()
/**
* 請求失敗后的錯誤統(tǒng)一處理
* @param {Number} status 請求失敗的狀態(tài)碼
*/
const errorHandle = (response) => {
? const status = response.data.code
? const msg = response.data.msg
? // 狀態(tài)碼判斷
? switch (status) {
? ? // 401:未登錄狀態(tài)或token過期,跳轉(zhuǎn)登錄頁
? ? case 401:
? ? ? Message.error(msg)
? ? ? break
? ? case 403:
? ? ? alert(msg)
? ? ? break
? ? ? // 404:請求不存在
? ? case 404:
? ? ? router.push({
? ? ? ? path: '/404'
? ? ? })
? ? ? Message.error('請求的資源不在')
? ? ? break
? ? ? // 服務(wù)器錯誤
? ? case 500:
? ? ? Message.error(msg)
? ? ? break
? ? default:
? ? ? console.log(msg)
? }
}
// 創(chuàng)建axios實例
var instance = axios.create({
? timeout: 1000 * 100
})
/**
* 請求攔截器
* 每次請求前,如果存在token則在請求頭中攜帶token
*/
instance.interceptors.request.use(
? request => {
? ? // // 該位置會獲取登陸成功時的token數(shù)據(jù)
? ? const token = window.sessionStorage.getItem('token')
? ? /* 判斷用戶是否已經(jīng)登錄 */
? ? if (token) {
? ? ? /* 請求頭添加token信息 */
? ? ? request.headers.Authorization = token
? ? ? if (request.url.includes('ossUpload')) {
? ? ? ? request.timeout = 600000
? ? ? } else {
? ? ? ? request.timeout = 100000
? ? ? }
? ? ? /* 判斷token是否即將過期 */
? ? ? /*? `oauth/token`是刷新token的接口,只有當(dāng)token將要過期且不是請求刷新token的接口才會進入 */
? ? ? if (tokenIsOverdue() && !request.url.includes('oauth/token')) {
? ? ? ? /* 首先所有的請求來了,我們要先判斷當(dāng)前是否正在刷新,如果不是,將刷新的標志置為true并請求刷新token;如果是,將請求存儲到數(shù)組中 */
? ? ? ? if (!window.isRefreshing) {
? ? ? ? ? window.isRefreshing = true
? ? ? ? ? loginApi.apiLogin(
? ? ? ? ? ? qs.stringify({
? ? ? ? ? ? ? grant_type: 'refresh_token',
? ? ? ? ? ? ? refresh_token: window.sessionStorage.getItem('refresh'),
? ? ? ? ? ? ? client_id: 'trade',
? ? ? ? ? ? ? client_secret: 'homedone'
? ? ? ? ? ? })
? ? ? ? ? ).then(res => {
? ? ? ? ? ? if (res.data.code === 200) {
? ? ? ? ? ? ? /* 將刷新的token替代老的token */
? ? ? ? ? ? ? request.headers.Authorization = 'Bearer ' + res.data.data.access_token
? ? ? ? ? ? ? /* 更新內(nèi)存的token信息 */
? ? ? ? ? ? ? const resData = res.data.data
? ? ? ? ? ? ? window.sessionStorage.setItem('token', 'Bearer' + ' ' + resData.access_token) // 存入token Bearer
? ? ? ? ? ? ? window.sessionStorage.setItem('refresh', resData.refresh_token) // 存入刷新token,用來替換過期token
? ? ? ? ? ? ? window.sessionStorage.setItem('tokenTime', res.data.data.expires_in) // token有效期,秒級
? ? ? ? ? ? ? window.sessionStorage.setItem('tokenQueryTime', Math.floor(res.data.timestamp / 1000)) // token獲取時間,秒級
? ? ? ? ? ? ? /* 執(zhí)行數(shù)組里的請求,重新發(fā)起被掛起的請求 */
? ? ? ? ? ? ? onRrefreshed(res.data.data.access_token)
? ? ? ? ? ? } else { // 目前做的處理,當(dāng)替換token請求失敗時
? ? ? ? ? ? ? if (res.status === 401) { // 當(dāng)刷新token時為401,標識refresh_token也已經(jīng)過期,需重新登錄
? ? ? ? ? ? ? ? Message.warning('登陸過期請重新登陸!')
? ? ? ? ? ? ? ? window.location = '#/' // 重定向回登錄頁
? ? ? ? ? ? ? ? window.sessionStorage.clear() // 清除包括token的所有緩存,讓用戶重新登錄
? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? window.isRefreshing = true
? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? })
? ? ? ? ? const retry = new Promise((resolve, reject) => {
? ? ? ? ? ? subscribeTokenRefresh((token) => {
? ? ? ? ? ? ? request.headers.Authorization = 'Bearer ' + token
? ? ? ? ? ? ? /* 將請求掛起 */
? ? ? ? ? ? ? resolve(request)
? ? ? ? ? ? })
? ? ? ? ? })
? ? ? ? ? return retry
? ? ? ? }
? ? ? } else {
? ? ? ? return request
? ? ? }
? ? } else {
? ? ? /* 如果沒有登錄直接返回請求 */
? ? ? return request
? ? }
? ? return request
? },
? error => {
? ? return Promise.reject(error)
? }
)
// 響應(yīng)攔截器
instance.interceptors.response.use(
? // 請求成功
? res => {
? ? res.status === 200 ? Promise.resolve(res) : Promise.reject(res)
? ? return res
? },
? // 請求失敗
? error => {
? ? const {
? ? ? response
? ? } = error
? ? if (response) {
? ? ? // 請求已發(fā)出,但是不在2xx的范圍
? ? ? errorHandle(response)
? ? ? return Promise.reject(response)
? ? } else {
? ? ? return Promise.reject(error)
? ? }
? }
)
export default instance