首页 > 其他分享 >项目中前端如何实现无感刷新 token!

项目中前端如何实现无感刷新 token!

时间:2023-10-12 16:59:55浏览次数:29  
标签:refreshToken 请求 accessToken 过期 response token 刷新 无感

1、问题

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

2、原因

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

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

3、切入点

  1. 怎样及时的、在用户感知不到的情况下更新token
  2. 当 token 失效的情况下,出错的请求可能不仅只有一个;当失效的 token 更新后,怎样将多个失败的请求,重新发送?

3、前要

1、我们仅从前端的角度去处理。
2、后端提供了两个重要的参数:accessToken(用于请求头中,进行鉴权,存在有效期);refreshToken(刷新令牌,用于更新过期的 accessToken,相对于 accessToken 而言,它的有效期更长)。

4、具体实现

4.1、处理 axios 响应拦截

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

// 响应拦截
axiosInstance.interceptors.response.use(
  (response) => {
    return response;
  },
  (error) => {
    let {
      data, config
    } = error.response;
    return new Promise((resolve, reject) => {
      /**
       * 判断当前请求失败
       * 是否由 toekn 失效导致的
       */
      if (data.statusCode === 401) {
         /**
         * refreshToken 为封装的有关更新 token 的相关操作 
         */
        refreshToken(() => {
          resolve(axiosInstance(config));
        });
      } else {
        reject(error.response);
      }
    })
  }
)
  1. 我们通过判断statusCode来确定,是否当前请求失败是由token过期导致的;
  2. 使用 Promise 处理将失败的请求,将由于 token 过期导致的失败请求存储起来(存储的是请求回调函数,resolve 状态)。理由:后续我们更新了 token 后,可以将存储的失败请求重新发起,以此来达到用户无感的体验

补充:

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

原因

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

2、发起的更新token的请求采用的也是处理后的axios,因此响应失败的拦截,对更新请求同样适用
问题:
这样会造成,当refreshToken过期后,会出现停留在首页,无法跳转到登录页面。
解决方法
针对这种现象,我们需要完善一下axios中响应拦截的逻辑。

axiosInstance.interceptors.response.use(
  (response) => {
    return response;
  },
  (error) => {
    let {
      data, config
    } = error.response;
    return new Promise((resolve, reject) => {
      /**
       * 判断当前请求失败
       * 是否由 toekn 失效导致的
       */
      if (
        data.statusCode === 401 &&
        config.url !== '/api/token/refreshToken'
      ) {
        refreshToken(() => {
          resolve(axiosInstance(config));
        });
      } else if (
        data.statusCode === 401 &&
        config.url === '/api/token/refreshToken'
      ) {
        /**
         * 后端 更新 refreshToken 失效后
         * 返回的状态码, 401
         */
        window.location.href = `${HOME_PAGE}/login`;
      } else {
        reject(error.response);
      }
    })
  }
)

4.2、封装 refreshToken 逻辑

要点:

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

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

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

// 存储由于 token 过期导致 失败的请求
let expiredRequestArr: any[] = [];

/**
 * 存储当前因为 token 失效导致发送失败的请求
 */
const saveErrorRequest = (expiredRequest: () => any) => {
  expiredRequestArr.push(expiredRequest);
}

// 避免频繁发送更新 
let firstRequre = true;
/**
 * 利用 refreshToken 更新当前使用的 token
 */
const updateTokenByRefreshToken = () => {
  firstRequre = false;
  axiosInstance.post(
    '更新 token 的请求',
  ).then(res => {
    let {
      refreshToken, accessToken
    } = res.data;
    // 更新本地的token
    localStorage.setItem('accessToken', accessToken);
    // 更新请求头中的 token
    setAxiosHeader(accessToken);
    localStorage.setItem('refreshToken', refreshToken);

    /**
     * 当获取了最新的 refreshToken, accessToken 后
     * 重新发起之前失败的请求
     */
    expiredRequestArr.forEach(request => {
      request();
    })
    expiredRequestArr = [];
  }).catch(err => {
    console.log('刷新 token 失败err', err);
    /**
     * 此时 refreshToken 也已经失效了
     * 返回登录页,让用户重新进行登录操作
     */
    window.location.href = `${HOME_PAGE}/login`;
  })
}

/**
 * 更新当前已过期的 token
 * @param expiredRequest 回调函数,返回由token过期导致失败的请求
 */
export const refreshToken = (expiredRequest: () => any) => {
  saveErrorRequest(expiredRequest);
  if (firstRequre) {
    updateTokenByRefreshToken();
  }
}

补充:

问题:
1、怎么能保证当更新token后,在处理存储的过期请求时,此时没有过期请求还在存呢?;万一此时还在expiredRequestArr推失败的请求呢?

解决方法

我们需要调整一下更新 token的逻辑,确保当前由于过期失败的请求都接收到了,再更新token然后重新发起请求。

最终结果:

// refreshToken.ts

/**
 * 功能:
 *  用于实现无感刷新 token
 */
import { axiosInstance, setAxiosHeader } from "@/axios"
import { CLIENT_ID, HOME_PAGE } from "@/systemInfo"

// 存储由于 token 过期导致 失败的请求
let expiredRequestArr: any[] = [];

/**
 * 存储当前因为 token 失效导致发送失败的请求
 */
const saveErrorRequest = (expiredRequest: () => any) => {
  expiredRequestArr.push(expiredRequest);
}

/**
 * 执行当前存储的由于过期导致失败的请求
 */
const againRequest = () => {
  expiredRequestArr.forEach(request => {
    request();
  })
  clearExpiredRequest();
}

/**
 * 清空当前存储的过期请求
 */
export const clearExpiredRequest = () => {
  expiredRequestArr = [];
}

/**
 * 利用 refreshToken 更新当前使用的 token
 */
const updateTokenByRefreshToken = () => {
  axiosInstance.post(
    '更新请求url',
    {
      clientId: CLIENT_ID,
      userName: localStorage.getItem('userName')
    },
    {
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
        'Authorization': 'bearer ' + localStorage.getItem("refreshToken")
      }
    }
  ).then(res => {
    let {
      refreshToken, accessToken
    } = res.data;
    // 更新本地的token
    localStorage.setItem('accessToken', accessToken);
    localStorage.setItem('refreshToken', refreshToken);
    setAxiosHeader(accessToken);
    /**
     * 当获取了最新的 refreshToken, accessToken 后
     * 重新发起之前失败的请求
     */
    againRequest();
  }).catch(err => {
    /**
     * 此时 refreshToken 也已经失效了
     * 返回登录页,让用户重新进行登录操作
     */
    window.location.href = `${HOME_PAGE}/login`;
  })
}

let timer: any = null;
/**
 * 更新当前已过期的 token
 * @param expiredRequest 回调函数,返回过期的请求
 */
export const refreshToken = (expiredRequest: () => any) => {
  saveErrorRequest(expiredRequest);
  // 保证再发起更新时,已经没有了过期请求要进行存储
  if (timer) clearTimeout(timer);
  timer = setTimeout(() => {
    updateTokenByRefreshToken();
  }, 500);
}
// 响应拦截 区分登录前
axiosInstance.interceptors.response.use(
  (response) => {
    return response;
  },
  (error) => {
    let {
      data, config
    } = error.response;
    return new Promise((resolve, reject) => {
      /**
       * 判断当前请求失败
       * 是否由 toekn 失效导致的
       */
      if (
        data.statusCode === 401 &&
        config.url !== '/api/token/refreshToken'
      ) {
        refreshToken(() => {
          resolve(axiosInstance(config));
        });
      } else if (
        data.statusCode === 401 &&
        config.url === '/api/token/refreshToken'
      ) {
        /**
         * 后端 更新 refreshToken 失效后
         * 返回的状态码, 401
         */
        clearExpiredRequest();
        window.location.href = `${HOME_PAGE}/login`;
      } else {
        reject(error.response);
      }
    })
  }
)

 

标签:refreshToken,请求,accessToken,过期,response,token,刷新,无感
From: https://www.cnblogs.com/yejt/p/17759871.html

相关文章

  • TMS刷新后Buffer队列被清空
    前言。。。。。少叙。。。症状按SAP标准配置了传输请求,导入传输请求(addtobuffer),这时在buffer/SID下能看到加入的TR请求但在STMS刷新后,buffer/SID里的文件被刷新,信息显示为 Troubleshooting 。。。。。(凭老司机猜测,你信吗)解决方案测试10有8-9是在同一个传输系统......
  • 前端大文件上传如何做到刷新续传?
    前言这两天在学习阿里云oss上传。踩了不少坑,终于实现了大文件分片、断点续传的功能。这篇文章主要分享学习笔记,希望能给大家一些帮助。先看效果 技术栈1.前端:react+Ts+axios上传文件2.Node部分:定义接口、阿里云oss3.socket.io:实时同步上传进度特别说明axios中on......
  • 监听上传的服务器文件是否改变,从而刷新页面
     监听上传的服务器文件是否改变,从而刷新页面=>interfaceOptions{timer?:number;}classUpdater{oldScript:string[];//存储第一次值也就是script的hash信息newScript:string[];//获取新的值也就是新的script的hash信息dispatch:Record<string,Fun......
  • 统一图像和文字生成的MiniGPT-5来了:Token变Voken,模型不仅能续写,还会自动配图了
    前言 OpenAI的GPT-5大模型似乎还遥遥无期,但已经有研究者率先推出了创新视觉与语言交叉生成的模型MiniGPT-5。这对于生成具有连贯文本描述的图像具有重要意义。本文转载自机器之心仅用于学术分享,若侵权请联系删除欢迎关注公众号CV技术指南,专注于计算机视觉的技术总结、最新......
  • 刷新iframe
    对于iframe(iframeMain是iframe的ID)document.all.iframeMain.src="XXX";//显示iframe中最后一次刷新得到的内容document.all.iframeMain.contentWindow.location="XXX";//重新刷新iframe的内容firefox下没有document.all对象要用document.getEle......
  • Editor窗口刷新常用代码
     EditorApplication.RepaintAnimationWindow();//刷新Animation窗口EditorApplication.RepaintProjectWindow();//刷新Project窗口EditorApplication.RepaintHierarchyWindow();//刷新hierarchy窗口InternalEditorUtility.RepaintAllViews();//刷新所有窗口SceneVie......
  • client-go实战之六:时隔两年,刷新版本继续实战
    欢迎访问我的GitHub这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos时隔两年,《client-go实战》被激活,更多内容将会继续更新时间过得真快,《client-go实战》系列已是两年前的作品,近期工作中再次用到client-go时,突然发现自己原创的内容远达不......
  • laravel8对接阿里云sdk刷新cdn缓存接口RefreshObjectCaches
    <?phpnamespaceApp\Admin\Forms;useEncore\Admin\Widgets\Form;useIlluminate\Http\Request;useAlibabaCloud\Client\AlibabaCloud;useAlibabaCloud\Client\Exception\ClientException;useAlibabaCloud\Client\Exception\ServerException;......
  • 显示器的刷新率和分辨率哪个更重要
    显示器的刷新率和分辨率哪个更重要刷新频率60基本上算是基本设定;人眼40以上基本上感觉不出卡顿。之所以说越高的刷新频率越好,是因为刷新频率越高跟显卡的同步性越好,表现出来就是越跟手;这在FPS类游戏中有一定的差异。如果不是为了打游戏,分辨率越高越好;当然我不建议你被......
  • pig4cloud框架系列三:密码模式换取token(登录认证)
    1,通过apiFox或者postMan模拟调用接口,使用密码模式获取token 2,首先代码会先来到ProviderManager类的authenticate方法,也就是登录认证的入口 3,先到AuthenticationProvider接口,然后到AbstractUserDetailsAuthenticationProvider实现类的authenticate方法 4,authenticate方法......