
最近项目用户登录页面处于安全考虑,登录之后的access_token并非永久有效而是有实效性了,因此 需要在用户登录之后token失效的情况下进行access_token的无感知刷新,也既是需要前端在access_token失效时调用一次refresh API使得access_token再次有效,而不影响用户的使用体验。
需求拆解
用户登录成功之后API会将{ access_token: '', refresh_token: '', expired: '' }返回,那就可以在用户发起一个请求时,判断access_token是否过期,过期就刷新access_token。首先判断access_token过期可以在请求之前根据expired判断,也可以在请求结果回来之后判断,问题的难点其实是指如何在多个请求进来时将这些请求暂存下来,然后在refresh之后在做请求场景还原。
实现思路
针对需求拆解主要需要解决两个问题,一是失效判断,二是多个请求还原,具体解法如下:
失效判断:(1)请求前根据expired的过期时间进行判断,但这种 *** 作是不可靠的,因为时间是可修改的,所以不采用该方法;(2)根据请求结果判断access_token过期,然后进行刷新 *** 作,然后在进行一次请求。
多请求的场景还原:当同时有多个过期请求进来时,需要避免多次refresh的请求,设置一个标志位,当已经有refresh的 *** 作在进行时,再进来的请求进行挂起(此时可以利用Promise的pending状态做文章),等待refresh成功之后在进行请求。
伪代码实现如下
import axios, { AxiosRequestConfig } from 'axios'
interface IRequestConfig {
tokenInfoKey: string// 由于登录之后的 token 数据时存在 localstorage 需要键值
onRefreshError: VoidFunction
}
interface ITokenInfo {
access_token: string
refresh_token: string
expired: string
}
class Request {
private config: IRequestConfig = {}
private tokenInfo: ITokenInfo | null = null
private isRefreshing: boolean = false
private needRetryRequest = []
constructor (config: IRequestConfig) {
const { tokenInfoKey } = config
this.tokenInfo = JSON.parse(localStorage.getItem(tokenInfoKey))
this.config = config
}
refreshAccessToken (onSuccess: VoidFunction) {
return axios.request<ITokenInfo>({
url: '',
method: '',
})
.then((res) =>{
localStorage.setItem(this.config.tokenInfoKey, res.data)
onSuccess()
})
.catch(() =>{
this.needRetryRequest = []
})
}
request (config: AxiosRequestConfig) {
if (this.isRefreshing) {
return new Promise((resolve) =>{
this.needRetryRequest.push(async () =>await resolve(axios))
})
}
return axios
.request(config)
.then(res =>{
return res.data
})
.catch(e =>{
// 假设 401401 定义需要 refresh
const code = 401401
const { status } = e.response
if (status === code) {
this.isRefreshing = true
this.refreshAccessToken(() =>{
Promise.all([() =>this.request(config), ...this.needRetryRequest].map(cb =>cb()))
})
}
})
.finally(() =>{
this.isRefreshing = false
})
}
}
需求
当token过期的时候,刷新token,前端需要做到无感刷新token,即刷token时要做到用户无感知,避免频繁登录。实现思路
方法一
后端返回过期时间,前端判断token过期时间,去调用刷新token接口
缺点:需要后端额外提供一个token过期时间的字段;使用了本地时间判断,若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败。
方法二(可行)
写个定时器,定时刷新token接口
缺点:浪费资源,消耗性能,不建议采用。
方法三 (推荐)
在响应拦截器中拦截,判断token 返回过期后,调用刷新token接口
思考:
我在遇到同样问题的时候也是考虑了两种方法,一种是定时刷新一种是过期时刷新。 但是我选择了定时刷新的方案。 假如token 的过期时间是5分钟,那么在高频率使用的情况下(每秒访问接口)每隔5分钟就会刷新一次token。如果web端有大屏展示页面的话,过期刷新的方案跟定时刷新的方案调用的token次数其实一样。但过期刷新的时候可能会阻碍接口的请求,导致每隔5分钟会出现一次接口变慢的情况。 测试下发现,用定时刷新的方案并不会浪费资源,唯一的代价就是需要运行一个定时器。
后端每个需要token的接口,都返回token的失效时间指的是定时器的时间也是后端返回token失效的时间
实现:
axios的基本骨架,利用service.interceptors.response进行拦截
importaxiosfrom'
axios'service.interceptors.response.use(response=>
{
if(response.data.code ===409) {
returnrefreshToken({
refreshToken:localStorage.getItem('refreshToken'),token: getToken() })
.then(res=> {
const{ token } = res.data
setToken(token)
response.headers.Authorization =`${token}`}).catch(err=> {
removeToken()
router.push('/login')
returnPromise.reject(err)
}) }
return response && response.data },(error) =>{
Message.error(error.response.data.msg)
returnPromise.reject(error)
})
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)