前端如何实现无感知刷新token

3,259 阅读2分钟

前言

最近用户一直抱怨,用户操作我们平台,好好的突然就回到登录页面了,需要用户重新登录,这个就是因为token 失效了,导致后端验证不通过,返回401权限问题。 后端的实现是JWT Token机制,如果不清楚这个机制,可以看一下我前面发的一篇文章 JWT的Token认证机制:juejin.cn/post/703785…

解决方案

前端如何实现无感知刷新token:思路是 每一个接口请求的时候去拦截,然后判断当前用户的token时效,如果小于一半的时间,就自动调用刷新token 的接口。如果后端设计的是如果token过期还能去刷新token那其实只要判断过期之后再去请求,我们的后端定的是token过期是不能换取token,最终一起商量就是小于有效时间一半就刷新token

我这个项目是多页面,每个页面都存在 请求代理axios 和fetch,甚至原生的请求都有,所以我把请求拦截的方法单独一个文件,然后所以每个页面模块的main.js 都引入了proxy.js 文件,两种方式我都用代码列出来

proxy.js

import moment from 'moment';

import Cookies from 'js-cookie';


let $requestRepeatRenewalToken = false; // 是否已经调用了刷新token的接口

const TokenKey = 'h3_token';

// 设置cookie
setCookie2 (name: string, value: string, hours = 8) {

  const d = new Date();

  d.setTime(d.getTime() + hours * 60 * 60 * 1000);

  Cookies.set(name, value, {

    expires: d,

  });

}

// 续期token
export const renewalToken = async (token) => {

  const content = parseToken(token);

  const expired = content.exp * 1000; // s -> ms

  if (expired) {

    const expiredTime = moment(expired);

    const currentTime = moment();

    const hours = expiredTime.diff(currentTime, 'hours');

    if (hours < 4) { // 剩余时间小于4小时的时候续期

      const res: any = await RenewalToken(token);

      console.log('RenewalToken resp: ', res);

      setTimeout(() => { // 防止刷新页面的时候 调用多次,所以设置定时器

        $requestRepeatRenewalToken = false;

      }, 5000);

      if (res && res.errcode == 0) {

        // 续期成功,刷新cookie

        const token = res.result.token;

        setCookie2(TokenKey, token);

      } else {
        console.error(res.errmsg);
      }
    }
  }
};
 
// 续期token 判断逻辑
export const fliterRenewalTokenHandle = (innerArgs) => {

  // 每次接口请求需要去判断token过期时间,如果小于4小时,需要续期token,注意需要过滤续期的接口,否则会一直调用续期的接口

  try {

    const renewalTokenRequestUrl = 'xxx';// 刷新token 接口

    if (innerArgs && innerArgs[0] && innerArgs[0].currentTarget && innerArgs[0].currentTarget.responseURL){

      if (innerArgs[0].currentTarget.responseURL.indexOf(renewalTokenRequestUrl) === -1){

        const token = getToken();

        renewalToken(token);

      }

    }

  } catch (e){

    console.log('处理续期接口异常');

  }

};
 

const proxyAjax = () => {

  if (!XMLHttpRequest) {

    return;

  }

  const nativeAjaxSend = XMLHttpRequest.prototype.send;

  XMLHttpRequest.prototype.send = function (...args) {

    const oldCb = this.onreadystatechange;

    const token = getToken();

    if (token) {

      this.setRequestHeader('authorization', `Bearer ${token}`);

    }

    this.onreadystatechange = (...innerArgs) => {

      if (!$requestRepeatRenewalToken) { // 这里如果采用防抖处理,多个并行发送请求 接口还是会导致多次调用,所以用变量

        $requestRepeatRenewalToken = true;

        fliterRenewalTokenHandle(innerArgs);

      }

      if (this.readyState === 4) {

        switch (this.status) {

          case 401:

            // token 失效, 清除cookie中token, 跳转到登录页

            // console.log('401 found');

            clearAuthorization();

            //TODO

            break;

          default:

            const authorization = this.getResponseHeader('Authorization');

            // 判断是否有更新的token

            if (authorization) {

              setCookie2(TokenKey, authorization.replace('Bearer ', ''));

            }

            // const h3Token = getCookie(TokenKey);

            // h3Token && renewalToken(h3Token);

            break;

        }

      }

      oldCb && oldCb.apply(this, innerArgs);

    };

    return nativeAjaxSend.apply(this, args);

  };

};

export default proxyAjax();

或者axios 请求拦截

axios.interceptors.request.use((config) => {

  config.headers.authorization=`Bearer ${token}`;

  // to do 上面相关代码

   if (!$requestRepeatRenewalToken) { // 这里如果采用防抖处理,多个并行发送请求 接口还是会导致多次调用,所以用变量

        $requestRepeatRenewalToken = true;

        fliterRenewalTokenHandle(innerArgs);

   }

  ……

}, error => {

  Promise.reject(error);

});

结语

其实成熟的方案是双token机制,即access token 和 refresh token。

access token 是临时的,有一定有效期。这是因为,access token 在使用的过程中可能会泄露。给 access token 限定一个较短的有效期可以降低因 access token 泄露而带来的风险。

refresh token 一定要保存在使用方的服务器上,而绝不能存放在移动 app、PC端软件、浏览器上,也不能在网络上随便传递。

过期时间: refresh token > access token 一般是2倍以上

简单来说前端调用接口是用 access token,后端去判断 access token如果过期了,即给前端access token 重置一个值,同时后端refresh token 也要重置,如果access token和refresh token都过期了就需要用户重新登录或鉴权…… 这种处理token的机制完全是后端处理。

参考:www.cnblogs.com/blowing00/p…