token过期了怎么办?

token过期了怎么办?一般做法是重复第一次获取token的过程(比如登录,扫描授权等) ,这样做的缺点是用户体验不好,每一小时强制登录一次几乎是无法忍受的。那应该怎么办呢?其实这是一个老生常谈的问题,但是最近发现很多人并不清楚,所以今天就一次讲清这个问题!

token 过期处理

没有绝对的安全, 所谓的安全处理, 就是提高攻击者攻击的难度, 对他造成了一定的麻烦, 我们这个网站就是安全的! 网站安全性就是高的! 所以: token 必须要有过期时间!

token 过期问题

目标: 了解token过期问题的存在, 学习token过期的解决思路

现象:

你登陆成功之后,接口会返回一个token值,这个值在后续请求时带上(就像是开门钥匙)。

但是,这个值一般会有有效期(具体是多长,是由后端决定),在我们的项目中,这个有效期是2小时。

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

思考:

  1. token需要过期时间吗 ?

    token即是获取受保护资源的凭证,当然必须有过期时间。否则一次登录便可永久使用,认证功能就失去了其意义。非但必须有个过期时间,而且过期时间还不能太长,

    参考各个主流网站的token过期时间,一般1小时左右

    token一旦过期, 一定要处理, 不处理, 用户没法进行一些需要授权页面的使用了

  2. token过期该怎么办?

    token过期,就要重新获取。

    那么重新获取有两种方式,一是重复第一次获取token的过程(比如登录,扫描授权等) ,这样做的缺点是用户体验不好,每一小时强制登录一次几乎是无法忍受的。

    那么还剩第二种方法,那就是主动去刷新token. 主动刷新token的凭证是refresh token,也是加密字符串,并且和token是相关联的。相比可以获取各种资源的token,refresh token的作用仅仅是获取新的token,因此其作用和安全性要求都大为降低,所以其过期时间也可以设置得长一些。

目标效果 - 保证每一小时, 都是一个不同的token

第一次请求 9:00 用的是 token1第二次请求 12:00 用的是 token2

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

  • token:

    • 作用:在访问一些接口时,需要传入token,就是它。
    • 有效期:2小时。
  • refresh_token

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

对于 某次请求A 的响应,如果是401错误

  • 有refresh_token,用refresh_token去请求回新的token

    • 新token请求成功

      • 更新本地token
      • 再发一次请求A
    • 新token请求失败

      • 清空vuex中的token
      • 携带请求地址,跳转到登陆页
  • 没有refresh_token

    • 清空vuex中的token
    • 携带请求地址,跳转到登陆页

对于一个请求的响应 401, 要这么处理, 对于十个请求的响应 401, 也要这么处理,

我们可以统一将这个token过期处理放在响应拦截器中

请求拦截器: 所有的请求, 在真正被发送出去之前, 都会先经过请求拦截器 (可以携带token)

响应拦截器: 所有的响应, 在真正被(.then.catch await)处理之前, 都会先经过响应拦截器, 可以在这个响应拦截器中统一对响应做判断

响应拦截器处理token

目标: 通过 axios 响应拦截器来处理 token 过期的问题

响应拦截器: www.kancloud.cn/yunye/axios...

  1. 没有 refresh_token 拦截到登录页, 清除无效的token

测试: {"token":"123.123.123"}

php 复制代码
// 添加响应拦截器
http.interceptors.response.use(function (response) {
  // 对响应数据做点什么 (成功响应) response 就是成功的响应 res
  return response
}, function (error) {
  // 对响应错误做点什么 (失败响应) 处理401错误
  // console.dir(error)
  if (error.response.status === 401) {
    console.log('token过期了, 一小时过去了, 需要通过refresh_token去刷新token')
    // 获取 refresh_token, 判断是否存在, 存在就去刷新token
    const refreshToken = store.state.tokenInfo.refresh_token
    if (refreshToken) {
      console.log('存在refreshToken, 需要进行刷新token操作')
    } else {
      // 没有refreshToken, 直接去登录, 将来还能跳回来
      // router.currentRoute 指向当前路由信息对象 === 等价于之前页面中用的 this.$route
      // 清除本地token, 跳转登录 (无意义的本地token内容, 要清除)
      store.commit('removeToken')
      router.push({
        path: '/login',
        query: {
          backto: router.currentRoute.fullPath
        }
      })
    }
  }
  return Promise.reject(error)
})

提供清除token的mutation

scss 复制代码
// 移出tokenInfo的信息, 恢复成空对象
removeToken (state) {
  state.tokenInfo = {}
  // 更新到本地, 本地可以清掉token信息
  removeToken()
},
  1. 有 refresh_token 发送请求, 刷新token

测试操作: 将 token 修改成 xyz, 模拟 token 过期, 而有 refresh_token 发现401, 会自动帮你刷新token

{"refresh_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDYzNTcyODcsInVzZXJfaWQiOjExMDI0OTA1MjI4Mjk3MTc1MDQsInJlZnJlc2giOnRydWV9.2A81gpjxP_wWOjclv0fzSh1wzNm6lNy0iXM5G5l7TQ4","token":"xyz"}

php 复制代码
const refreshToken = store.state.tokenInfo.refresh_token
if (refreshToken) {
  console.log('存在refreshToken, 需要进行刷新token操作')
  // (1) 发送请求, 进行刷新token操作, 获取新的token
  // 注意: 这边发请求, 不用http实例, 用它会自动在请求前帮你携带token(会覆盖你的refresh_token)
  // 这边, 直接用 axios 发送请求
  const res = await axios({
    method: 'put',
    url: 'http://ttapi.research.itcast.cn/app/v1_0/authorizations',
    // 请求头中携带refresh_token信息
    headers: {
      Authorization: `Bearer ${refreshToken}`
    }
  })
  const newToken = res.data.data.token
  // (2) 将新token更新到vuex中
  store.commit('setTokenInfo', {
    refresh_token: refreshToken,
    token: newToken
  })
} 
  1. 刷新token后, 应该重新发送刚才的请求 (让用户刷新token无感知)
lua 复制代码
return http(error.config)
  1. 那万一 refresh_token 也过期了, 是真正的用户登录过期了 (一定要让用户重新登录的)

测试: {"refresh_token":"123.123","token":"123.123.123"} 修改后, 修改的是本地, 记得刷新一下

从哪拦走的, 就回到哪去

php 复制代码
// 添加响应拦截器
http.interceptors.response.use(function (response) {
  // 对响应数据做点什么 (成功响应) response 就是成功的响应 res
  return response
}, async function (error) {
  // 对响应错误做点什么 (失败响应) 处理401错误
  // console.dir(error)
  if (error.response.status === 401) {
    console.log('token过期了, 一小时过去了, 需要通过refresh_token去刷新token')
    // 获取 refresh_token, 判断是否存在, 存在就去刷新token
    const refreshToken = store.state.tokenInfo.refresh_token
    if (refreshToken) {
      try {
        console.log('存在refreshToken, 需要进行刷新token操作')
        // (1) 发送请求, 进行刷新token操作, 获取新的token
        // 注意: 这边发请求, 不用http实例, 用它会自动在请求前帮你携带token(会覆盖你的refresh_token)
        // 这边, 直接用 axios 发送请求
        const res = await axios({
          method: 'put',
          url: 'http://ttapi.research.itcast.cn/app/v1_0/authorizations',
          // 请求头中携带refresh_token信息
          headers: {
            Authorization: `Bearer ${refreshToken}`
          }
        })
        const newToken = res.data.data.token
        // (2) 将新token更新到vuex中
        store.commit('setTokenInfo', {
          refresh_token: refreshToken,
          token: newToken
        })
        // (3) 重新发送刚才的请求, http, 自动携带token (携带的是新token)
        //     error.config就是之前用于请求的配置对象, 可以直接给http使用
        return http(error.config)
      } catch {
        // refresh_token 过期了, 跳转到登录页
        // 清除过期的token对象
        store.commit('removeToken')
        // 跳转到登录页, 跳转完, 将来跳回来
        router.push({
          path: '/login',
          query: {
            backto: router.currentRoute.fullPath
          }
        })
      }
    } else {
      // 没有refreshToken, 直接去登录, 将来还能跳回来
      // router.currentRoute 指向当前路由信息对象 === 等价于之前页面中用的 this.$route
      // 清除本地token, 跳转登录 (无意义的本地token内容, 要清除)
      store.commit('removeToken')
      router.push({
        path: '/login',
        query: {
          backto: router.currentRoute.fullPath
        }
      })
    }
  }
  return Promise.reject(error)
})

注意点:

  1. 响应拦截器要加在axios实例 http 上。
  2. 用refresh_token请求新token时,要用axios,不要用实例 http (需要: 手动用 refresh_token 请求)
  3. 得到新token之后,再发请求时,要用 http 实例 (用token请求)
  4. 过期的 token 可以用 refresh_token 再次更新获取新token, 但是过期的 refresh_token 就应该从清除了
相关推荐
kyriewen10 分钟前
豆包和千问同时关了智能体,我用它们搭的 3 个自动化全废了——迁移方案整理
前端·javascript·ai编程
前端一小卒23 分钟前
我用 TypeScript 从零手写了一个 Claude Code,然后发现它的核心只有 30 行
前端·agent
大圣编程2 小时前
Python中continue语句的用法是什么?
开发语言·前端·python
yuhaiqiang2 小时前
随手 vibecoding 的浏览器插件已经 6000 多次下载,聊聊他的产品设计
前端·后端·面试
之歆3 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
再吃一根胡萝卜3 小时前
如何把小米 MiMo 接入 CodeBuddy,打造私有 Agent
前端
负责的蛋挞4 小时前
异步HttpModule的实现方式
java·服务器·前端
丹宇码农7 小时前
把 HLS 字幕玩出花:zwPlayer 如何让 M3U8 视频支持全文搜索、翻译与码率自适应
前端·javascript·音视频·hls·视频播放器
2501_943782357 小时前
【共创季稿事节】猜数字游戏:二分法思维与交互式反馈
前端·游戏·microsoft·harmonyos·鸿蒙·鸿蒙系统