/edu-boss-fed

vue 综合项目

Primary LanguageVue

axios 搭配 Refresh Token 重新获取 access token

Refresh token 用来在 access token 过期后去获取新的 access token,refresh token 过期时间相对 access token 较长

重新获取 access token 流程

发起请求 -> 接口返回 access token 已过期状态码 -> 用之前存储的 refresh token 发起获取新的 access token 的请求 -> 拿到新的 access token 和 refresh token,用新的 access token 和 refresh token 替换旧的 access token 和 refresh token

注意事项

  • 在 access token 过期,用 refresh token 获取新的 access token 期间可能会有多个请求已经发出,当 access token 获取成功之后需要将这些请求重新发送出去

  • refresh token 只能使用一次,因此需要在使用 refresh token 获取 access token 时添加一个标识,标记正在获取 access token,避免其他过期的接口重复发起请求

封装 axios

const request = axios.create({})

// 封装重新获取 token 方法 
function refreshToken () {
  return axios.create()({
    method: 'POST',
    url: '/refresh_token',
    data: qs.stringify({
      refreshtoken: store.state.user.refresh_token
    })
  })
}

// 请求拦截器
request.interceptors.request.use(function (config) {
  const { user } = store.state
  if (user && user.access_token) {
    config.headers.Authorization = user.access_token
  }

  // 注意:这里一定要返回 config,否则请求就发不出去了
  return config
}, function (error) {
  return Promise.reject(error)
})

let isRfreshing = false // 控制刷新 token 的状态
let requests: any[] = [] // 存储刷新 token 期间过来的 401 请求
// 响应拦截器
request.interceptors.response.use(function (response) {
  // 状态码为 2xx 都会进入这里
  // 如果是自定义错误状态码,错误处理就写到这里
  return response
}, async function (error) {
  // 超出 2xx 状态码都都执行这里
  // 如果是使用的 HTTP 状态码,错误处理就写到这里
  if (error.response) {
    // 请求发出去收到响应了,但是状态码超出了 2xx 范围
    const { status } = error.response
    if (status === 400) {
      Message.error('请求参数错误')
    } else if (status === 401) {
      // token 无效(没有提供 token、token 是无效的、token 过期了)
      // 如果有 refresh_token 则尝试使用 refresh_token 获取新的 access_token
      if (!store.state.user) {
        redirectLogin()
        return Promise.reject(error)
      }

      // 刷新 token
      if (!isRfreshing) {
        isRfreshing = true // 开启刷新状态
        // 尝试刷新获取新的 token
        return refreshToken().then(res => {
          if (!res.data.success) {
            throw new Error('刷新 Token 失败')
          }

          // 刷新 token 成功了
          store.commit('setUser', res.data.content)
          // 把 requests 队列中的请求重新发出去
          requests.forEach(cb => cb())
          // 重置 requests 数组
          requests = []
          return request(error.config)
        }).catch(err => {
          console.log(err)
          store.commit('setUser', null)
          redirectLogin()
          return Promise.reject(error)
        }).finally(() => {
          isRfreshing = false // 重置刷新状态
        })
      }

      // 刷新状态下,把请求挂起放到 requests 数组中
      // 当 token 刷新成功后,调用 requests 数组内存储的方法,resolve 重新发起的请求
      // 重新发出的请求成功后,外层的 Promise 就可以拿到接口重新请求返回的值
      return new Promise(resolve => {
        requests.push(() => {
          resolve(request(error.config))
        })
      })
    } else if (status === 403) {
      Message.error('没有权限,请联系管理员')
    } else if (status === 404) {
      Message.error('请求资源不存在')
    } else if (status >= 500) {
      Message.error('服务端错误,请联系管理员')
    }
  } else if (error.request) {
    // 请求发出去没有收到响应
    Message.error('请求超时,请刷新重试')
  } else {
    // 在设置请求时发生了一些事情,触发了一个错误
    Message.error(`请求失败:${error.message}`)
  }

  // 把请求失败的错误对象继续抛出,扔给上一个调用者
  return Promise.reject(error)
})