前后端分离中的无痛刷新token机制

今天我们来说一说前后端分离中的无痛刷新token机制,在手机app中应该经常用到,

大家都知道在前后端是以token的形式交互,既然是token,那么肯定有它的过期时间,没有一个token是永久的,永久的token就相当于一串永久的密码,是不安全的,

那么既然有刷新时间,问题就来了

  • 前后端交互的过程中token如何存储?
  • token过期时,前端该怎么处理
  • 当用户正在操作时,遇到token过期该怎么办?直接跳回登陆页面?😅(你确定这样用户不会打死你吗,老子好不容易表单填完准备提交????)

token如何存储

cookie的大小约4k,兼容性在ie6及以上 都兼容,在浏览器和服务器间来回传递,因此它得在服务器的环境下运行,而且可以设定过期时间,默认的过期时间是session会话结束。
localStorage的大小约5M,兼容性在ie7及以上都兼容,有浏览器就可以,不需要在服务器的环境下运行, 会一直存在,除非手动清除 。

对于这个问题,答案大致分为2种

  1. 存在 cookie
  2. 存在 localStorage

我觉得都可以,两种我都用😄

token过期时,前端该怎么处理

思路:token过期处理方式大概就是:

  1. 第一种:跳回登陆页面重新登陆
  2. 第二种:catch 401 ,然后重新获取 token

对于第一种,很简单在vue中我们可以在 axios 拦截器中这样写:

instance.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么
    return response.data
  },
  function (error) {
    console.log(error)

    if (error.response) {
      if (error.response.status === 401) {
            Message.error('登陆过期请重新登陆!')
            setToken('')
            router.push({
              name: 'login'
            })
        }
      }
    }

    // 对响应错误做点什么
    return Promise.reject(error.response)
  }
)

对于第二种,如何重新获取 token,这就要涉及到后端的知识了

来先让我讲一段废话😅

现代认证和/或授权解决方案已将令牌的概念引入其协议中。令牌是经过特殊处理的数据,它们可以提供足够的信息来授权用户执行操作,或者允许客户端获取有关授权过程的其他信息(然后完成它)。换句话说,令牌是允许执行授权过程的信息。客户端(或授权服务器以外的任何一方)是否可读取或解析此信息是由实现定义的。重要的是:客户端获取此信息,然后使用它来访问资源。JSON Web令牌(JWT)规范 定义了一种可以由实现表示公共令牌信息的方式。

JWT定义了一种方式,其中可以表示与认证/授权过程有关的某些公共信息。顾名思义,数据格式是JSON。JWT具有某些常见字段,例如主题,发行者,到期时间等。当与其他规范(如JSON Web签名(JWS)JSON Web加密(JWE))结合使用时,JWT变得非常有用。这些规范不仅提供了授权令牌通常所需的所有信息,而且还提供了验证令牌内容的方法,使其不会被篡改(JWS)和加密信息以使其保持不透明的方式给客户(JWE)。数据格式的简单性(及其它优点)帮助JWT成为最常见的令牌类型之一。如果您有兴趣学习如何在Web应用程序中实现JWT,请查看Ryan Chenkie的这篇优秀文章

出于本文的目的,我们将关注两种最常见的令牌类型:访问令牌刷新令牌

  • 访问令牌携带必要的信息以直接访问资源。换句话说,当客户端将访问令牌传递给管理资源的服务器时,该服务器可以使用令牌中包含的信息来决定客户端是否被授权。访问令牌通常具有到期日期并且是短暂的。
访问令牌
  • 刷新令牌包含获取新访问令牌所需的信息。换句话说,每当访问令牌需要访问特定资源时,客户端可以使用刷新令牌来获得由认证服务器发布的新访问令牌。常见用例包括在旧的访问令牌过期后获取新访问令牌,或者首次访问新资源。刷新令牌也可以过期,但相当长寿。刷新令牌通常受到严格的存储要求,以确保它们不会泄露。它们也可以被授权服务器列入黑名单。
刷新令牌

标记是否不透明通常由实现定义。通用实现允许针对访问令牌进行直接授权检查。也就是说,当访问令牌被传递到管理资源的服务器时,服务器可以读取令牌中包含的信息并自己决定用户是否被授权(不需要对授权服务器进行检查)。这是必须签署令牌的原因之一(例如,使用JWS)。另一方面,刷新令牌通常需要检查授权服务器。这种处理授权检查的分割方式允许三件事:

  1. 改进了授权服务器的访问模式(降低负载,加快检查速度)
  2. 泄漏访问令牌的访问窗口较短(这些访问令牌很快过期,减少了泄露令牌允许访问受保护资源的机会)
  3. 滑动会话(见下文)

滑动会话是在一段时间不活动后到期的会话。可以想象,使用访问令牌和刷新令牌可以轻松实现。当用户执行操作时,将发出新的访问令牌。如果用户使用过期的访问令牌,则该会话将被视为非活动状态,并且需要新的访问令牌。是否可以使用刷新令牌获取此令牌或者需要新的身份验证轮次是由开发团队的要求定义的。

上文摘抄自[刷新令牌:何时使用它们以及它们如何与JWT交互

废话一堆,简单的来说就是:

服务器生成token的过程中,会有两个时间,一个是token失效时间,一个是token刷新时间,刷新时间肯定比失效时间长,当用户的 token 过期时,你可以拿着过期的token去换取新的token,来保持用户的登陆状态,当然你这个过期token的过期时间必须在刷新时间之内,如果超出了刷新时间,那么返回的依旧是 401

所以要实现无痛刷新token,我们应该这样

  1. 在axios的拦截器中加入token刷新逻辑
  2. 当用户token过期时,去向服务器请求新的 token
  3. 把旧的token替换为新的token
  4. 然后继续用户当前的请求
  5. 用户体验棒棒哒💯

上代码

在axios的拦截器中加入token刷新逻辑

instance.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么
    return response.data
  },
  function (error) {
    console.log(error)

    if (error.response) {
      if (error.response.status === 401) {
       
        // 如果当前路由不是login,并且用户有 “记住密码” 的操作
        // 那么去请求新 token
        if (router.currentRoute.name !== 'login') {
          if (getRemember() && getRefreshToken()) {

            return doRequest(error)
          } else {

            Message.error('登陆过期请重新登陆!')
            setToken('')
            router.push({
              name: 'login'
            })
          }
        }
      }
    }

    // 对响应错误做点什么
    return Promise.reject(error.response)
  }
)
async function doRequest (error) {
  const data = await store.dispatch('refreshToken')

  return res
}

// refreshToken 中重新设置了 token 和  refresh_token
commit('setToken', { token, expiresIn })
setRefreshToken(token, refreshTtl / (60 * 60 * 24))

来看测试

为了方便测试,我们手动清除了 token 来造成token 过期的效果

refresh

可以看到手动清除token之后,系统自动去refresh了token,而不是跳到登录页面

但是问题又来了

可以看出用户本身要去请求 articles 的接口,并没有再重复请求。请脑补:用户点击了文章列表,但是系统好像 “没反应” ???

所以接下来,我们不仅要刷新token而且要再次发送用户上次的请求

上代码😼

async function doRequest (error) {
  const data = await store.dispatch('refreshToken')
  let { token_type: tokenType, access_token: accessToken } = data

  let token = tokenType + accessToken
  let config = error.response.config
  
  config.headers.Authorization = token

  const res = await axios.request(config)

  return res
}

这里我们一定要用同步的方法来进行这一系列操作!!(比如 async / await)

来看演示👇

refreshAndRequest

到此就结束了,相信大佬们一定都懂了吧👏

原文来自我的博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容