axios刷新token后页面无值

axios刷新token后页面无值,第1张

背景

最近项目用户登录页面处于安全考虑,登录之后的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) 

                    })


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/bake/11539792.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-16
下一篇2023-05-16

发表评论

登录后才能评论

评论列表(0条)

    保存