无感刷新token流程

1.采用双token机制【access_token+refresh_token】

   a.登录成功之后,返回这两个 token;

第一步返回双token.png

   b.访问接口时带上 access_token 访问;

第二步拿access_token登录鉴权.png

   c.当 access_token 过期时,通过 refresh_token 来刷新,拿到新的 access_token 和 refresh_token

第三步刷新双token.png

有了 refresh_token 之后,只要带上这个 token 就能标识用户,不需要传用户名密码就能拿到新 token。

而 access_token 一般过期时间设置的比较短,比如 30 分钟,refresh_token 设置的过期时间比较长,比如 7 天。

这样,只要你 7 天内访问一次,就能刷新 token,再续 7 天,一直不需要登录。

但如果你超过 7 天没访问,那 refresh_token 也过期了,就需要重新登录了

具体执行

1.在响应拦截器中通过response 返回的状态码判断状态,比如401说明token过期

2.如果获取不到【刷新】令牌,说明未登录状态,如果获取到【刷新】令牌则执行刷新令牌操作

执行顺序和逻辑

  1. 初始请求返回401:首次遇到需要刷新令牌的情况时,将isRefreshToken设置为true并尝试刷新令牌。
  2. 等待刷新:此时,其他进来的请求检测到isRefreshTokentrue,知道令牌正在刷新,因此将自己加入到requestList队列中,等待令牌刷新完成。
  3. 令牌刷新成功:一旦令牌刷新成功,执行requestList.forEach(cb => cb()),遍历执行队列中的所有请求,这时使用的是新令牌。
  4. 令牌刷新失败:如果令牌刷新失败,根据具体的错误处理逻辑,可能直接清空队列(避免重复请求)并处理登录状态。

请求重放逻辑.png

以下为响应拦截器代码

// 响应拦截器
axiosInstance.interceptors.response.use(async res => {
        // 未设置状态码则默认成功状态
        const code = res.data.code || 200;
        // 获取错误信息
        const msg = res.data.msg
        if (code === 401) {
            // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
            if (!isRefreshToken) {
                isRefreshToken = true;
                // 1. 如果获取不到刷新令牌,则只能执行登出操作
                // setTimeout(() =>{},1000)
                if (getRefreshToken() == null || getRefreshToken().trim().length == 0) {
                    isRefreshToken = false
                    return handleAuthorized();
                }
                // 2. 进行刷新访问令牌
                try {
                    const refreshTokenRes = await refreshToken()
                    if (refreshTokenRes.code == 0){
                        // 2.1 刷新成功,则回放队列的请求 + 当前请求
                        setToken(refreshTokenRes.data)
                        requestList.forEach(cb => cb())
                        return axiosInstance(res.config)
                    }
                } catch (e) {// 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
                    // 2.2 刷新失败,只回放队列的请求
                    // requestList.forEach(cb => cb())
                    // 提示是否要登出。即不回放当前请求!不然会形成递归
                    requestList = []
                    isRefreshToken = false
                    return handleAuthorized();
                    // return;
                } finally {
                    requestList = []
                    isRefreshToken = false
                }
            } else {
                // 添加到队列,等待刷新获取到新的令牌
                return new Promise(resolve => {
                    requestList.push(() => {
                        res.config.headers['Authorization'] = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
                        resolve(axiosInstance(res.config))
                    })
                })
            }
        }else if (code !== 200) {
            if (msg === '无效的刷新令牌' || msg === '用户未扫码') { // hard coding:忽略这个提示,直接登出
            } else {
                Notification.error({
                    title: msg
                })
            }
            return Promise.reject('error')
        } else {
            return res
        }
    }, error => {
        let {message} = error;
        if (message === "Network Error") {
            message = "系统正在维护升级中";
        } else if (message.includes("timeout")) {
            message = "系统接口请求超时";
        } else if (message.includes("Request failed with status code")) {
            if (message.substr(message.length - 3) === '401'){
                return handleAuthorized()
            }else {
                message = "系统接口" + message.substr(message.length - 3) + "异常";
            }
        }
        Message({
            message: message,
            type: 'error',
            duration: 5 * 1000
        })
        return Promise.reject(error)
    }
)

function handleAuthorized() {
    if (!window.location.href.endsWith("loginPage")){//loginPage页面不显示提示
        Message({
            message: '无效的会话,或者会话已过期,请重新登录。',
            type: 'warning',
            duration: 1 * 1000
        })
    }

    return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
}
发表评论 / Comment

用心评论~