34

Generally, for some systems that need to record user behaviors, they will be required to pass the login token when making network requests. However, for the security of interface data, the server's token is generally not set too long, and it is usually 1-7 days as needed. After the token expires, you need to log in again. However, frequent login will cause a bad experience. Therefore, if you need a good experience, you need to refresh the token regularly and replace the previous token.

To achieve a senseless refresh of the token, there are three main solutions:

Option One:

The back end returns the expiration time, and the front end judges the expiration time of the token every time it requests. If the expiration time is almost reached, it calls the refresh token interface.

Disadvantages : The backend needs to provide an additional token expiration time field; local time judgment is used. If the local time is tampered with, especially when the local time is slower than the server time, the interception will fail.

Method Two

Write a timer, and then refresh the token interface regularly.
Disadvantages : Waste resources, consume performance, not recommended.

Method Three

Intercept in the request response interceptor, after judging that the token return expires, call the refresh token interface.

Combining the above three methods, the best is the third one, because it does not require additional resources. Next, let's look at using axios to make network requests, and then respond to the interception of service.interceptors.response.

import axios from 'axios'

service.interceptors.response.use(
  response => {
    if (response.data.code === 409) {
        return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: getToken() }).then(res => {
          const { token } = res.data
          setToken(token)
          response.headers.Authorization = `${token}`
        }).catch(err => {
          removeToken()
          router.push('/login')
          return Promise.reject(err)
        })
    }
    return response && response.data
  },
  (error) => {
    Message.error(error.response.data.msg)
    return Promise.reject(error)
  }
)

Question 1: How to prevent the token from being refreshed multiple times

In order to prevent the token from being refreshed multiple times, a variable isRefreshing can be used to control whether the status of the token is being refreshed.

import axios from 'axios'

service.interceptors.response.use(
  response => {
    if (response.data.code === 409) {
      if (!isRefreshing) {
        isRefreshing = true
        return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: getToken() }).then(res => {
          const { token } = res.data
          setToken(token)
          response.headers.Authorization = `${token}`
        }).catch(err => {
          removeToken()
          router.push('/login')
          return Promise.reject(err)
        }).finally(() => {
          isRefreshing = false
        })
      }
    }
    return response && response.data
  },
  (error) => {
    Message.error(error.response.data.msg)
    return Promise.reject(error)
  }
)

2. How to refresh the token when two or more requests are initiated at the same time

When the second expired request comes in and the token is being refreshed, we first store this request in an array queue, find a way to keep this request waiting, and wait until the token is refreshed and then try to clear the request queue one by one.
So how do you make this request awaiting? In order to solve this problem, we have to resort to Promise. After the request is stored in the queue, a Promise is returned at the same time, so that the Promise is always in the Pending state (that is, the resolve is not called). At this time, the request will wait and wait. As long as we do not execute the resolve, the request will always be wait. When the interface of the refresh request comes back, we call resolve and try again one by one.

import axios from 'axios'

// 是否正在刷新的标记
let isRefreshing = false
//重试队列
let requests = []
service.interceptors.response.use(
  response => {
  //约定code 409 token 过期
    if (response.data.code === 409) {
      if (!isRefreshing) {
        isRefreshing = true
        //调用刷新token的接口
        return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: getToken() }).then(res => {
          const { token } = res.data
          // 替换token
          setToken(token)
          response.headers.Authorization = `${token}`
           // token 刷新后将数组的方法重新执行
          requests.forEach((cb) => cb(token))
          requests = [] // 重新请求完清空
          return service(response.config)
        }).catch(err => {
        //跳到登录页
          removeToken()
          router.push('/login')
          return Promise.reject(err)
        }).finally(() => {
          isRefreshing = false
        })
      } else {
        // 返回未执行 resolve 的 Promise
        return new Promise(resolve => {
          // 用函数形式将 resolve 存入,等待刷新后再执行
          requests.push(token => {
            response.headers.Authorization = `${token}`
            resolve(service(response.config))
          })
        })
      }
    }
    return response && response.data
  },
  (error) => {
    Message.error(error.response.data.msg)
    return Promise.reject(error)
  }
)

xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2、《Android应用开发实战》和《鸿蒙HarmonyOS应用开发实践》