401错误的场景

有如下两种情况会出现401错误:

  1. 未登陆用户做一些需要权限才能做的操作,代码会报出401错误。这种情况下,应该让用户回到登陆页。
  2. 登录用户的token过期了

整体目标是:通过axios响应拦截器来处理401问题。

理解token过期

登陆成功之后,接口会返回一个token值,这个值在后续请求时通过请求头时带上(就像是开门钥匙)。但是,这个值一般会有有效期(具体是多长,是由后端决定),假如在我这里有效期是2小时。

如果你上午8点登陆成功,到了10:01分,则token就会失效,再去发请求时,就会报401错误。

refresh_token和token的作用

当用户登陆成功之后,返回的token中有两个值,说明如下:

android retrofit 处理token过期 token过期怎么处理_js

  • token:
  • 作用:在访问一些接口时,需要传入token,就是它。
  • 有效期:2小时(安全)。
  • refresh_token
  • 作用: 当token的有效期过了之后,可以使用它去请求一个特殊接口(这个接口也是后端指定的,明确需要传入refresh_token),并返回一个新的token回来(有效期还是2小时),以替换过期的那个token。
  • 有效期:14天。(最理想的情况下,一次登陆可以持续14天。)

响应拦截器功能

axios中提供了响应拦截器功能:所有从后端回来的响应都会集中进入响应拦截器中。所以,我们可以在响应拦截器中去写代码来统一解决。

android retrofit 处理token过期 token过期怎么处理_前端_02


request的响应拦截器中:

  • 对于某次请求A,如果是401错误 (2)
  • 有refresh_token,用refresh_token去请求回新的token (3)
  • 新token请求成功 (4)
  • 更新本地token (5)
  • 再发一次请求A (6)
  • 新token请求失败
  • 携带请求地址,跳转到登陆页
  • 没有refresh_token,说明没有登陆
  • 携带请求地址,跳转到登陆页

封装独立的history

src/utils/history

import { createBrowserHistory } from 'history'

const history = createBrowserHistory()

export default history

App.tsx

import history from '@/utils/history'

function App () {
  return (
    <div className="App">
      <Router history={history}>

处理401问题,token无感知刷新

request.ts

// 添加响应拦截器
instance.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么
    return response
  },
  async function (error: AxiosError<{ message: string }>) {
    // 对响应错误做点什么
    // console.dir(error)
    // 1. 处理网络异常状况
    // 如果请求延迟,response为undefined
    if (!error.response) {
      Toast.show('网络异常')
      return Promise.reject(error)
    }
    // 2. 返回401(token过期)处理
    if (error.response.status !== 401) {
      Toast.show('操作异常') 
      return Promise.reject(error)
    }
    if (error.response.status === 401) {
      // 2.1 获取refresh_token, 发送请求,获取最新的token
      const { refresh_token } = getToken() // 从本地中获取refresh_token 
      if (refresh_token) {
        try {
          // 发请求重新获取token
          const res = await axios.put(baseURL + 'authorizations', null, {
            headers: {
              Authorization: `Bearer ${refresh_token}`
            }
          })
          // 2.2 保存到redux
          store.dispatch(
            savaToken({ token: res.data.data.token, refresh_token })
          )
          // 2.3 这个时候已经有token了,重新发请求
          return instance(error.config)
        } catch (error) {
          // 如果使用refresh_token获取token的请求都失败了,就直接跳转到login
          history.replace('/login', { from: location.pathname })
          // 清空redux中的所有token
          store.dispatch(savaToken({ token: '', refresh_token: '' }))
          // 将错误抛出
          return Promise.reject(error)
        }
      } else {
        // 2.4 没有refresh_token 跳转到login,并携带当前页面的地址,支持回跳
        history.replace('/login', { from: location.pathname })
      }
      // 2.5 清除 token 和 refresh_token 相关的数据
      store.dispatch(savaToken({ token: '', refresh_token: '' }))
      // 2.6 抛出错误
      return Promise.reject(error)
    }
    return Promise.reject(error)
  }
)