前端实现token刷新
应用场景:在一个管理系统中,当用户登录进来后,我们期望用户在操作时,不会因为token过期而被迫登出。但token是有时效的,这时候我们就需要一个刷新token的操作来保障用户的登录状态。1. 原理3. 完整代码request.tsimport axios, { AxiosInstance } from 'axios'import router from '@/router'import stor
应用场景:在一个管理系统中,当用户登录进来后,我们期望用户在操作时,不会因为token过期而被迫登出。但token是有时效的,这时候我们就需要一个刷新token的操作来保障用户的登录状态;而当用户长时间未操作,则可以被登出。
1. 原理
-
登录,从后台获取到token(鉴权令牌),refresh_token(刷新token的令牌),expire_time(token的时效)。将这三个以及登录的时间点(loginTime)存储下来,以备使用。
-
用户操作中,向后台发送请求,每次请求时,将当前请求时间(currentTime)与loginTime和expire_time对比,
即(currentTime-loginTime)得到的时间段即将接近或超出expire_time时,使用refresh_token去重新获取token。
注:此处需要知道的是,refresh_token与token一样,都是有时效的。但refresh_token的时效必定长于token,这样token即便过期了,也不会影响refresh_token。因此只要用户在refresh_token的有效期内向后台发送请求,token就可以一直得到刷新。
而用户长时间未操作,refresh_token也过期了,这时候就可以被正常登出。 -
使用refresh_token去重新获取token的操作实际上就是再次进行了一次登录操作,只不过这次的参数并非账密,而是refresh_token,
并且这个操作用户是不知情的。每次登录获取到的鉴权信息都会覆盖上一次存储的鉴权信息,这样就会确保token和refresh_token一直都是最新的。
大体流程就是以上三个步骤循环。
2. 思路图
3. 核心代码
request.ts
import axios, { AxiosInstance } from 'axios'
import router from '@/router'
import store from '@/store'
import { message } from 'ant-design-vue'
import { getToken } from '@/utils/auth'
import { refreshToken } from '@/utils/refreshToken'
// create an axios instance
const service = axios.create({
baseURL: '/api',
timeout: 30000
})
// 请求拦截器
service.interceptors.request.use(
(config: any) => {
if (store.getters.token) {
config.headers['Authorization'] = getToken()
}
// 登录,不校验token
if (config.url.indexOf('/login') > -1) {
return config
} else {
let interval = null
let retry = new Promise((resolve, reject) => {
const refreshFun = () => refreshToken().then(res => {
// 判断是否刷新token,且成功了
if (res === 'success') {
console.log('刷新token!!!')
config.headers['Blade-Auth'] = `Bearer ${getToken()}`
// 挂起请求
resolve(config)
} else if (res === 'pending') {
console.log('等待刷新token!!!')
interval = setInterval(() => {
refreshFun()
}, 500)
} else { // 不需要刷新token或刷新失败
// 等待刷新成功后就不需要再刷新,此时需重新赋值新token
if (interval) {
console.log('等待成功!')
config.headers['Blade-Auth'] = `Bearer ${getToken()}`
clearInterval(interval)
}
// 挂起请求
resolve(config)
}
})
refreshFun()
})
return retry
}
},
error => {
return Promise.reject(error)
}
)
// 返回拦截器
service.interceptors.response.use(
response => {
if (response.data.success) {
// 若为登录接口,记录登录返回的时间
if (response.config.url.indexOf('/login') > -1) {
const time = String(new Date().getTime())
localStorage.setItem('loginTime', time)
}
return Promise.resolve(response.data)
}
response.data.msg && message.error(response.data.msg)
return Promise.reject(response.data.msg)
},
error => {
if (error.response.status && error.response.status === 401) {
store.dispatch('user/logout')
router.push('/login')
}
error.response.data.msg && message.error(error.response.data.msg)
return Promise.reject(error)
}
)
export default service as AxiosInstance
refreshToken.ts
import store from '@/store'
export async function refreshToken() {
const currentTime = new Date().getTime()
const loginTime = Number(localStorage.getItem('loginTime'))
const userInfo = localStorage.getItem('USER_INFO')
if (loginTime && userInfo) {
const { expires_in, refresh_token } = JSON.parse(localStorage.getItem('USER_INFO'))
const splitTime = expires_in - (currentTime - loginTime) / 1000
if (splitTime < 60) { // token过期时间小于1分钟时获取新token
let params = {
type: 'refresh_token',
refresh_token: refresh_token,
}
const refreshTokenStatus = localStorage.getItem('refreshTokenStatus')
// 确保同一时间段内只执行一次
if (!refreshTokenStatus) {
localStorage.setItem('refreshTokenStatus', 'true')
await store.dispatch('user/login', params).catch(function(err) {
return '' // 请求失败
})
// 请求成功,清除状态值
localStorage.removeItem('refreshTokenStatus')
return 'success'
} else { // 正在获取token,不再重复请求
return 'pending'
}
} else { // 未到过期时间,不请求
return ''
}
}
}
4. 注意点
-
登录成功,在登录接口的返回拦截器里记录登录时间
-
登出时清除以上所有存储的鉴权信息
-
多接口并发请求,且此时token已经过期。只需要在第一个接口里去请求刷新token,后面的接口先挂起,等到拿到最新的token后,更新请求头,发送请求。
做法:定义一个状态值,用来确保相近的时间段内不会重复请求刷新token -
长时间未操作,refresh_token过期,用户登出
5. 总结
这是个人总结一种方式,且已经应用在实际的项目中,暂时未出现问题。其实刷新token的方式是多种多样的,例如另一种方式是直接在主页面写个定时器,定时刷新token,这样较为简便,但却不适用于本项目。因此我们需要基于实际情况选择合适的方式 。
更多推荐
所有评论(0)