1. 背景知识了解

出于安全考虑,我们通过登陆操作获取到的token一般都会有一个有效时间,如果一直是静默状态,没有接口调用操作,时间到了之后token会失效,失效之后的token是不能作为用户的有效标识再去请求数据的,如果使用失效的token去请求接口数据,一般会报出401状态码

2. 开发者需要做什么

当用户并不知道token已经失效的情况下,继续进行操作,会出现401的状态,如果我们什么都不做,用户并不能知道当前发生了什么,所以我们有必要去帮助用户去获取新的token来替换失效的token,获取新token的方式一般有俩种,分别为:

  1. 强制用户跳回登录页进行登录,让用户通过登录操作进行新token获取(一般做法)
  2. 无感知刷新token 这种方案偏复杂,但是用户体验好,整体原理是,登录成功之后,我们会获取到俩个token数据,一个为当前token,一个为专门为了刷新token所需要的refreshToken,refreshToken的失效时间要远长于token,所以我们可以通过refreshToken去获取新的token,而不需要进行路由跳转跳回到登录页

接下来,我们分别看一下,俩种方案下的具体实施

3. 跳回登录页

原理

当token过期之后,我们使用过期token调用一个接口时,会返回401的状态码,我们一旦监听到当前是401的状态,就跳回到登录页,要求重新登录获取有效token

模拟token过期失效

我们正常登陆应用,然后随便打开一片文章,文章数据的获取是需要token的,然后我们去application中把token的部分删掉一部分,它就会变成无效token,回调详情页刷新浏览器让接口重新发起,然后查看接口状态

// utils/request.js
instance.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response
}, function (error) {
  // 对响应错误做点什么
  console.log(error.response.status) // 401
  if (error.response.status === 401) {
    router.push({
      path: '/login'
    })
  }
  return Promise.reject(error)
})

4.哪里出问题回到哪里

我们的接口是在哪个页面出现的401状态,等到用户重新登录之后,我们希望他能够回到刚才的页面,这就需要我们在跳回到登录页的时候,不光是跳转,还需要记录一下页面信息

1.跳转之前记录页面信息

instance.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response
}, function (error) {
  // 对响应错误做点什么
  console.log(error.response.status) // 401
  if (error.response.status === 401) {
    router.push({
      path: '/login',
      // 拿到当前页面完成路径包括参数,再跳转过去
      query: {
        backto: router.currentRoute.fullPath
      }
    })
  }
  return Promise.reject(error)
})

2.登录之后先判断再跳转

// 判断当前是否有sourceFrom字段 如果有则优先跳回到记录页
const backto = this.$route.query.backto
if (backto) {
    this.$router.push({
      path: backto
    })
} else {
    this.$router.push({
      path: '/'
    })
}

5.无感知刷新token *

对于某次请求A,如果是401错误,我们使用refreshToken
(后台接口返回的2个token,这是另外一个,专门用来做token过期的) 去获取新token

  • 新token请求成功
    • 更新本地token
    • 以之前的请求参数再发一次请求A
  • 新token请求失败(refreshtoken也失效了)
    • 清空vuex中的token
    • 携带请求地址,跳转到登陆页
// request.js
import store from '@/store'
import router from '@/router'

// 全局响应拦截
instance.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response
}, async function (error) {
  // 对响应错误做点什么
  if (error.response && error.response.status === 401) {
    const refreshToken = store.state.tokenInfo.refresh_token
    // 如果try语法块中出现错误,证明refreshToken也无效了
    try {
      const result = await axios({
        method: 'PUT',
        url: 'http://toutiao-app.itheima.net/v1_0/authorizations',
        headers: {
          Authorization: `Bearer ${refreshToken}`
        }
      })
      const newToken = result.data.data.token
      store.commit('setToken', {
        refresh_token: refreshToken,
        token: newToken
      })
      // 重新使用我们之间创建的axios实例,用本次错误请求中的配置项,再发一次
      return instance(error.config)
    } catch {
      // 路由跳转,进入登陆页
      router.push({
        path: '/login',
        query: {
          // currentRoute:表示当前路由
          backto: router.currentRoute.fullPath
        }
      })
    }
  }
  return Promise.reject(error)
})

以下只是个人做的小案例中常出现的问题,拿出来与大家分享。

Logo

前往低代码交流专区

更多推荐