• 无感刷新 token


    线上平台有时会出现用户正在使用的时候,突然要用户去进行登录,这样会造成很不好的用户体验,但是当时一直没有好的思路因此搁置了下来;通过零散时间查询资料以及思考,最终解决了这个问题,接下来跟大家分享一下!

    环境

    • 请求采用的 Axios V1.3.2。

    • 平台的采用的 JWT(JSON Web Tokens) 进行用户登录鉴权。

      拓展:JWT 是一种认证机制,让后台知道该请求是来自于受信的客户端;更详细的可以自行查询相关资料

    问题现象

    线上用户在使用的时候,偶尔会出现突然跳转到登录页面,需要重新登录的现象。

    原因

    • 突然跳转到登录页面,是由于当前的 token 过期,导致请求失败;在 axios 的响应拦截axiosInstance.interceptors.response.use中处理失败请求返回的状态码 401,此时得知token失效,因此跳转到登录页面,让用户重新进行登录。

    • 平台目前的逻辑是在 token 未过期内,用户登录平台可直接进入首页,无需进行登录操作;因此就存在该现象:用户打开平台,由于此时 token 未过期,用户直接进入到了首页,进行其他操作。但是在用户操作的过程中,token 突然失效了,此时就会出现突然跳转到登录页面,严重影响用户的体验感!
      注:目前线上项目中存在数据大屏,一些实时数据的显示;因此存在用户长时间停留在大屏页面,不进行操作,查看实时数据的情况

    切入点

    • 怎样及时的、在用户感知不到的情况下更新token

    • 当 token 失效的情况下,出错的请求可能不仅只有一个;当失效的 token 更新后,怎样将多个失败的请求,重新发送?

    操作流程

    好了!经过了一番分析后,我们找到了问题的所在,并且确定了切入点;那么接下来让我们实操,将问题解决掉。

    • 我们仅从前端的角度去处理。

    • 后端提供了两个重要的参数:accessToken(用于请求头中,进行鉴权,存在有效期);refreshToken(刷新令牌,用于更新过期的 accessToken,相对于 accessToken 而言,它的有效期更长)。

    1、处理 axios 响应拦截

    注:在我实际的项目中,accessToken 过期后端返回的 statusCode 值为 401,需要在axiosInstance.interceptors.response.use 的 error回调中进行逻辑处理。

    1. // 响应拦截
    2. axiosInstance.interceptors.response.use(
    3.   (response) => {
    4.     return response;
    5.   },
    6.   (error=> {
    7.     let {
    8.       data, config
    9.     } = error.response;
    10.     return new Promise((resolve, reject) => {
    11.       /**
    12.        * 判断当前请求失败
    13.        * 是否由 toekn 失效导致的
    14.        */
    15.       if (data.statusCode === 401) {
    16.          /**
    17.          * refreshToken 为封装的有关更新 token 的相关操作
    18.          */
    19.         refreshToken(() => {
    20.           resolve(axiosInstance(config));
    21.         });
    22.       } else {
    23.         reject(error.response);
    24.       }
    25.     })
    26.   }
    27. )
    1. 我们通过判断statusCode来确定,是否当前请求失败是由token过期导致的;

    2. 使用 Promise 处理将失败的请求,将由于 token 过期导致的失败请求存储起来(存储的是请求回调函数,resolve 状态)。

    理由:后续我们更新了 token 后,可以将存储的失败请求重新发起,以此来达到用户无感的体验

    补充:

    现象:在我过了几天登录平台的时候发现,refreshToken过期了,但是没有跳转到登录界面 原因

    • 当 refreshToken 过期失效后,后端返回的状态码也是 401

    • 发起的更新token的请求采用的也是处理后的axios,因此响应失败的拦截,对更新请求同样适用

    问题:

    这样会造成,当 refreshToken 过期后,会出现停留在首页,无法跳转到登录页面。

     

    解决方法

    针对这种现象,我们需要完善一下axios中响应拦截的逻辑。

    1. axiosInstance.interceptors.response.use(
    2.   (response) => {
    3.     return response;
    4.   },
    5.   (error=> {
    6.     let {
    7.       data, config
    8.     } = error.response;
    9.     return new Promise((resolve, reject) => {
    10.       /**
    11.        * 判断当前请求失败
    12.        * 是否由 toekn 失效导致的
    13.        */
    14.       if (
    15.         data.statusCode === 401 &&
    16.         config.url !== '/api/token/refreshToken'
    17.       ) {
    18.         refreshToken(() => {
    19.           resolve(axiosInstance(config));
    20.         });
    21.       } else if (
    22.         data.statusCode === 401 &&
    23.         config.url === '/api/token/refreshToken'
    24.       ) {
    25.         /**
    26.          * 后端 更新 refreshToken 失效后
    27.          * 返回的状态码, 401
    28.          */
    29.         window.location.href = `${HOME_PAGE}/login`;
    30.       } else {
    31.         reject(error.response);
    32.       }
    33.     })
    34.   }
    35. )

    2、封装 refreshToken 逻辑

    • 存储由于token过期导致的失败的请求。

    • 更新本地以及 axios 中头部的token

    • 当 refreshToken 刷新令牌也过期后,让用户重新登录。

    1. // 存储由于 token 过期导致 失败的请求
    2. let expiredRequestArr: any[] = [];
    3. /**
    4.  * 存储当前因为 token 失效导致发送失败的请求
    5.  */
    6. const saveErrorRequest = (expiredRequest: () => any=> {
    7.   expiredRequestArr.push(expiredRequest);
    8. }
    9. // 避免频繁发送更新
    10. let firstRequre = true;
    11. /**
    12.  * 利用 refreshToken 更新当前使用的 token
    13.  */
    14. const updateTokenByRefreshToken = () => {
    15.   firstRequre = false;
    16.   axiosInstance.post(
    17.     '更新 token 的请求',
    18.   ).then(res => {
    19.     let {
    20.       refreshToken, accessToken
    21.     } = res.data;
    22.     // 更新本地的token
    23.     localStorage.setItem('accessToken', accessToken);
    24.     // 更新请求头中的 token
    25.     setAxiosHeader(accessToken);
    26.     localStorage.setItem('refreshToken', refreshToken);
    27.     /**
    28.      * 当获取了最新的 refreshToken, accessToken 后
    29.      * 重新发起之前失败的请求
    30.      */
    31.     expiredRequestArr.forEach(request => {
    32.       request();
    33.     })
    34.     expiredRequestArr = [];
    35.   }).catch(err => {
    36.     console.log('刷新 token 失败err', err);
    37.     /**
    38.      * 此时 refreshToken 也已经失效了
    39.      * 返回登录页,让用户重新进行登录操作
    40.      */
    41.     window.location.href = `${HOME_PAGE}/login`;
    42.   })
    43. }
    44. /**
    45.  * 更新当前已过期的 token
    46.  * @param expiredRequest 回调函数,返回由token过期导致失败的请求
    47.  */
    48. export const refreshToken = (expiredRequest: () => any=> {
    49.   saveErrorRequest(expiredRequest);
    50.   if (firstRequre) {
    51.     updateTokenByRefreshToken();
    52.   }
    53. }

    总结

    经过一波分析以及操作,我们最终实现了实际项目中的无感刷新token,最主要的是有效避免了:用户在平台操作过程中突然要退出登录的现象(尤其是当用户进行信息填写,突然要重新登录,之前填写的信息全部作废,是很容易让人发狂的)。
     

  • 相关阅读:
    力扣刷题day38|完全背包问题总结、518零钱兑换 II、377组合总和 Ⅳ
    软件著作申请注意事项
    react 初体验
    Vue3中使用富文本编辑器
    什么是数据管理,数据治理,数据中心,数据中台,数据湖?
    酷开科技打造更好体验服务用户
    MySQL中的any_value()函数
    Vue中如何完成对axios的二次封装、统一接口管理
    es6 Promise, all, race 理解及应用
    爬虫基础——Scrapy(B站学习笔记)
  • 原文地址:https://blog.csdn.net/why_1639946449/article/details/133659407