axios无感知刷新token

前言

在写自己的小项目的时候,我后台用到了JWT,登录后会生成一个accessTokenrefreshTokenaccessToken用来进行认证,但是有一个失效时间,如果失效了会用refreshToken来换取新的accessToken。如果在用户正在访问网站的过程中accessToken失效,怎样实现后台自动刷新accessToken,而不会导致退出登录呢?

我的小项目的前端是用vue+axios来写的。接下来我们就开始吧~

思路

  • accessToken过期时,我的后台服务器返回的状态码是403。所以我们需要在axios的响应拦截器中处理。

  • 判断响应状态码是403的时候,我们先把当前的请求放到一个队列中保存起来,当token刷新完毕之后,再重新请求。我们可以创建一个Pormise对象,当Pormise对象的resolve方法未被调用时,会处于Pending状态

  • 上边说到把请求放到队列中保存,是因为,如果请求的比较频繁,在刷新token接口还没有返回结果的时候,又来了一个请求,那么这个新的请求也应该等待,在刷新token接口返回结果后,统一重新请求。

  • 这里我们还应该考虑到一种情况,就是请求比较频繁,在刷新token接口还没有返回结果的时候,又来了一个请求,这时候不应该再去刷新token。所以我们要用一个变量来标识当前是否在刷新token。

  • 执行完重新请求后,要将缓存的请求在队列中清空,在刷新完token后将标识置反。

代码

下面是完整的代码:

let isRefreshing = false  // 是否正在刷新token

let callbacks = [] // 失效后同时发送请求的容器 -- 缓存接口

// 刷新 token 后, 将缓存的接口重新请求一次
function onAccessTokenFetched(newToken) {
    callbacks.forEach(callback => {
        callback(newToken)
    })
    // 清空缓存接口
    callbacks = []
}

// 添加缓存接口
function addCallbacks(callback) {
    callbacks.push(callback)
}

// 响应拦截器
instance.interceptors.response.use(
    response => {
        if (response.data.code !== 200){
            MessageError(response.data.message)
        }
        return response.data
    },
    error => {
        /**
         * 将未授权接口缓存起来。retryOriginalRequest 这个 Promise 函数很关键,它一直处于等待状态。
         * 只有当token刷新成功后,onAccessTokenFetched 这个函数执行了回调函数,返回了 resolve 状态
         */
        if (error.response && error.response.status === 403){
            // 获取当前的请求
            let config = error.response.config
            // 
            const retryOriginalRequest = new Promise(resolve => {
                addCallbacks(newToken => {
                    // 表示用新的token去替换掉原来的token
                    config.headers.Authorization = newToken
                    resolve(instance.request(config)) // 调用resolve请求队列里面接口
                })
            })
            // 无感刷新Token
            if (!isRefreshing) {
                isRefreshing = true
                instance.post('/token/refresh', {refreshToken: getRefreshToken()}).then(response => {  // 用refreshToken获取新的token
                    let accessToken = response.data.accessToken
                    let refreshToken = response.data.refreshToken
                    setAccessToken(accessToken)
                    setRefreshToken(refreshToken)
                    onAccessTokenFetched(accessToken)
                }).catch(() => {
                    // 刷新token错误跳转到登陆页面
                    removeToken()
                    router.push('/login')
                }).finally(() => {
                    isRefreshing = false
                })
            }
            return retryOriginalRequest // 将token过期期间请求的接口包装成promise返回,等待刷新token后重新请求
        }else{
            MessageError(error.response.data.message)
            return Promise.reject(new Error(error.message || 'Error'))
        }
    }
)

axios无感知刷新token
https://www.zhaojun.inkhttps://www.zhaojun.ink/archives/1019
作者
卑微幻想家
发布于
2022-02-11
许可协议