Token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。
在vue项目中实现Token替换----前端学海
这里使用的vue中的请求拦截器,根据后端返回的Token的生成时间,以及该Token的有效期和当前发送请求的时间进行计算,判断当前Token是否快要过期,以确定是否开始请求新的Token。
计算公式为:Token有效时间 -(当前时间 - Token的生成时间)= 剩余的Token有效时间
这里我的判断为,Token剩余有效时间,小于五分钟的时候,调用刷新Token的接口,请求最新的Token。
在请求新Token期间,可能同时会有好几个请求同时发起,我们在这里将这些请求拦截下来,通过Promise,将这些请求,完整的防在待执行的请求队列中,
当新的Token请求成功,更新本地的Token之后,开始执行拦截的请求队列,并将这些请求的header中的Token替换为最新的Token,完成Token整个流程。
如果请求新Token接口失败,并且原Token已过了有效期时,提示登录过期,重定向到登录页,重新登录!
如有建议,欢迎在评论区指出!!!
请看代码
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
/* 存储请求的数组 */
const refreshSubscribers = []
/* 将所有的请求都push到数组中,其实数组是[function(token){}, function(token){},...] */
function subscribeTokenRefresh (cb) {
refreshSubscribers.push(cb)
}
/* 数组中的请求得到新的token之后自执行,用新的token去请求数据 */
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 // 已经过去的时间
// console.log(time, queryTime, Math.ceil(nowTime / 1000), arealyTime)
if ((time - arealyTime) < 300) { // token五分钟内即将过期时开启替换token,目前后台的token有效期为30分钟。
return true
} else {
return false
}
}
tokenIsOverdue()
/**
* 请求失败后的错误统一处理
* @param {Number} status 请求失败的状态码
*/
const errorHandle = (response) => {
const status = response.data.code
const msg = response.data.msg
// 状态码判断
switch (status) {
// 401:未登录状态或token过期,跳转登录页
case 401:
Message.error(msg)
break
case 403:
alert(msg)
break
// 404:请求不存在
case 404:
router.push({
path: '/404'
})
Message.error('请求的资源不在')
break
// 服务器错误
case 500:
Message.error(msg)
break
default:
console.log(msg)
}
}
// 创建axios实例
var instance = axios.create({
timeout: 1000 * 100
})
/**
* 请求拦截器
* 每次请求前,如果存在token则在请求头中携带token
*/
instance.interceptors.request.use(
request => {
// // 该位置会获取登陆成功时的token数据
const token = window.sessionStorage.getItem('token')
/* 判断用户是否已经登录 */
if (token) {
/* 请求头添加token信息 */
request.headers.Authorization = token
if (request.url.includes('ossUpload')) {
request.timeout = 600000
} else {
request.timeout = 100000
}
/* 判断token是否即将过期 */
/* `oauth/token`是刷新token的接口,只有当token将要过期且不是请求刷新token的接口才会进入 */
if (tokenIsOverdue() && !request.url.includes('oauth/token')) {
/* 首先所有的请求来了,我们要先判断当前是否正在刷新,如果不是,将刷新的标志置为true并请求刷新token;如果是,将请求存储到数组中 */
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
/* 更新内存的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获取时间,秒级
/* 执行数组里的请求,重新发起被挂起的请求 */
onRrefreshed(res.data.data.access_token)
} else { // 目前做的处理,当替换token请求失败时
if (res.status === 401) { // 当刷新token时为401,标识refresh_token也已经过期,需重新登录
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)
}
)
// 响应拦截器
instance.interceptors.response.use(
// 请求成功
res => {
res.status === 200 ? Promise.resolve(res) : Promise.reject(res)
return res
},
// 请求失败
error => {
const {
response
} = error
if (response) {
// 请求已发出,但是不在2xx的范围
errorHandle(response)
return Promise.reject(response)
} else {
return Promise.reject(error)
}
}
)
export default instance