如何优雅实现无感知令牌刷新(小程序+单页应用)

5,420 阅读5分钟

前言

在平时工作中,我们难免会遇到使用Token的场景,一般来讲是后台给我们一个Token,我们将这个Token存储到本地,每次请求去携带,但这样会有什么问题存在呢?

小程序场景

Token过期后的处理方式

假如现在我们在小程序中已经存储好了Token,那么每次请求会带上后台返回给我们的Token令牌:

令牌一般来讲,可能会存在过期的行为,那么我们这里也是要讨论Token过期后如何处理?

那么最笨的解决方法就是令牌过期后,让用户重新登录,然后重新生成令牌,但显然这种体验是很不好的!

那么相对用户体验更好的方法是什么呢,实际上我们可以当token过期后获取新的token,最后再帮用户请求一次之前请求失败的接口,我们把这种机制叫做二次重发机制

什么情况下会因为过期而产生问题?

对于服务端而言,绝大多数都要携带令牌,那么令牌一旦过期就意味着本次的接口也许是请求失败的,咱们考虑一个比较极端的情况,假如这次令牌的有效期是1小时,然后这个令牌对应的用户在这一个小时内一直在访问(在此期间是没有退出过小程序的),然后他在59分59秒时带着他刚刚有效期只有1个小时的令牌请求了服务端,那么由于http是耗时的,到了服务端后应该是已经过期了,然后服务器拿到了过期的令牌后肯定会拒绝访问,说这个令牌已经过期了,那这个时候会给用户一个很不好的体验,相信到了这里问题引申了出来,大家应该已经有了自己的解决思路,那下面就说说上面的二次重发机制如何实现?

二次重发机制

二次重发机制是为了让用户得到最好的用户体验(自动无感知的帮他重新刷新令牌,而不是重新登录)

其实也很好实现,大概思路就是:

将每次请求的Api都存起来 在获取到token失效后 重新获取新令牌,然后 根据存储的已请求API状态 将请求失败API重新请求一次

伪代码如下(仅供参考)

_request(url, resolve, reject, data = {}, method = 'GET', noRefetch = false) {
    wx.request({
      url: api.url,
      method: method,
      data: data,
      header: {
        Authorization: wx.getStorageSync('token');
      },
      success: (res) => {
        const code = res.statusCode
          if (code === 403) {
            if (!noRefetch) {
              _refetch(
                url,
                resolve,
                reject,
                data,
                method
              )
            }
          }
        }
    })
  }
  _refetch(...param) {
    getTokenFromServer((token) => {
      this._request(...param, true);
    });
  }

类似于下面的这张流程图 那么小程序由于不需要账号密码,有微信官方的登录接口支持,我们这样做是可以的,那如果是普通的web应用又该怎么办呢?

access_token和refresh_token双令牌实现无感知登录

在普通web单页应用开发中,也是后端给我们一个token,我们存起来,每次请求时携带验证,和刚刚小程序的例子一样(极端情况),令牌过期后,由于我们没有用户的账号密码,没有办法去让后台生成一个token,因为后台不知道你是谁,有的小伙伴肯定会说了,那在用户登录时,我们把用户的账号密码存起来不就可以了吗,到时候发送给服务端重新获取新的token,这样做是可以解决问题,但有很高的安全性问题,那么除了这种方式外还有什么方式呢?

其实就用到了我们这个大标题上面的双令牌机制

access_token

这个access_token怎么理解呢?可以理解为他就是用来验证用户身份的,把他传给后端,后端告诉你这个access_token是否有效

refresh_token

这个refresh_token怎么理解呢?他起到的是刷新的作用,那么他就可以避免让用户重新的输入账号和密码进行再次验证,

那这两个怎么结合使用呢?

如果我们遇到了access_token过期了,那么我们需要使用refresh_token去获取一个新的access_token,听起来是不是很简单,那现在如果refresh_token也过期了呢?那如果他过期了我们就只能让用户再次输入账号密码进行强身份验证了,那这个时候又打扰用户了,那下面我们就来说下如何合理的协调双令牌

那么正确的做法应该是两个令牌都有自己的过期时间,因为access_token是需要使用refresh_token刷新获取,所以refresh_token设置的过期时间要比access_token时间长,那么如何避免refresh_token过期呢?办法就是我们使用refresh_token刷新了access_token后,同时把refresh_token的过期时间也进行延长,

一般我们可以将access_token的过期时间设置为2小时的,refresh_token的过期时间设置为1个月,然后用户第一次进来,用了一段时间access_token过期了,过期后前端携带refresh_token去获取新的access_token,返回的新的access_token依旧是2小时,那么除此之外,refresh_token自身再刷新一次,刷新一次后他还是1个月的过期时间(不累加),这就保证了用户在一个月内只要访问了应用,就可以享受无感知的体验,

换句话说,如果用户1个月没打开过咱们的应用,那么咱们就没必要让他继续享受用户体验了,首先要保证他的账户安全,所以就让他输入账号密码进行强验证(相信大家在使用一些app的时候应该有遇到这种情况,这大概率就是使用了双令牌机制)。

总结

这就是一个比较完善的令牌和用户体验解决方案,有其他想法和看法的小伙伴可以在评论区提出哦!