前言

本人在开发项目时,在做登录模块时,参考了oauth2,在用户认证成功后会返回给前端一些令牌相关数据。接下来,再用进行接口请求时,前端根据令牌数据进行一系列的判断,然后做出最好的选择。
举个例子:

  • 假如后端生成的令牌的有效期是10min,刷新令牌的有效期是1h。
  • 当用户在10min内没有操作页面,但用户在最后一次操作后的1h内进行操作页面,那么前端就会使用刷新令牌请求后端获取新的令牌,然后携带新的令牌继续请求(底层实现,用户无感知)
  • 当用户操作时,距上一次操作1h后,那么刷新令牌也是无效的,前端会将用户跳转到登录页面进行登录操作。

实现

令牌格式

本人定义的格式如下,其中第一层数据是我封装全局响应,而data内的数据就是我们今天需要说明的令牌数据:

{"status":200,"code":"0","clientMessage":"执行成功","data":{"accessToken":"3aa501cd651945a9b4829ae166fca518","refreshToken":"6d1ceaf3f35d429491b273e7dd7e5daf","accessExpires":"2022-09-24 16:19:53","refreshExpires":"2022-09-26 15:19:53"},"dataMap":{},"timestamp":"2022-09-24 15:19:53"
}

这4个字段都有各自的用途:

  1. accessToken:访问令牌,请求接口时需要携带。
  2. refreshToken:刷新令牌,在访问令牌失效时使用。
  3. accessExpires:访问令牌的有效截止时间。
  4. refreshExpires:刷新令牌的有效截止时间。

后端准备

在整个无感刷新令牌中后端需要准备的东西并不多,只需要准备如下内容即可:

  1. 登录接口,返回认证信息(2个令牌和令牌各自的有效截止时间),格式参考上面的令牌格式
  2. 刷新令牌接口:根据刷新令牌获取新的令牌(2个令牌和令牌各自的有效截止时间),格式参考上面的令牌格式

前端准备

我这里使用的是Axios,在axios的请求拦截器中进行处理逻辑。

  1. 首先定义全局变量:
// 按照axios官方提示需要引入这两步(取消axios的请求)
const CancelToken = axios.CancelToken;
const source = CancelToken.source();// 全局变量,记录是否正在执行刷新令牌任务
let isRefreshing = false
// 需要取消请求的集合
const cancelTokens = []
  1. 定义Axios请求拦截器
service.interceptors.request.use(async config => {/*判断当前时间 accessExpires refreshExpires 三个时间1. 当前时间小于accessExpires 正常请求2. 当前时间大于accessExpires,但小于refreshExpires 先刷新令牌在正常请求3. 当前时间大于refreshExpires正常请求(响应拦截器会将其重定向登录页)*/// 自定义的方法,用来获取toke对象,token的格式就是上面令牌的格式(有4个属性)const token = LocalStorageUtil.getToken();// 判断当前请求接口既不是登录请求,也不是刷新令牌请求时if (validateUrlNotAuthentication(config.url)) {const now = new Date()// 判断是否需要请求刷新令牌接口(当前时间大于accessExpires,但小于refreshExpires 先刷新令牌在正常请求)const flag = token && validateDate(token.accessExpires) && validateDate(token.refreshExpires) && now > new Date(token.accessExpires) && now < new Date(token.refreshExpires)// 需要请求刷新令牌接口if (flag) {// 将本次请求放入待取消请求集合config.cancelToken = source.token;cancelTokens.push(() => source.cancel("令牌无效取消请求"));// 调用刷新令牌方法,开始刷新本地令牌await refreshingToken(token, config)}}// 在请求头中添加token(这里再次使用方法获取实时的令牌,因为刷新令牌会修改本地存储的token)if (LocalStorageUtil.getToken() && LocalStorageUtil.getAccessToken()) {// eslint-disable-next-line require-atomic-updatesconfig.headers[AUTHORIZATION] = BEARER + LocalStorageUtil.getAccessToken()}console.log("请求拦截器", config)return config
}, error => {// do something with request errorconsole.log(error) // for debugreturn Promise.reject(error)
})
  1. 定义Axios响应拦截器

service.interceptors.response.use(response => {console.log('响应拦截:', response)// 响应码const { status, config } = responseconst result = response.data// 响应码401,需要重新登录(或使用无感刷新token)if (status === 401) {// 清空用户登录状态store.dispatch('user/resetToken')// 跳转到登录页Router.push({ path: '/login'})return Promise.reject(result)}if (status < 200 || status >= 400) {// 设置响应为错误,return Promise.reject(result)}// 当是blob类型的响应时,返回完整的response,方便获取响应头等信息if (config.responseType && config.responseType === 'blob') {return Promise.resolve(response)}// 返回data内的数据(去掉axios和后端的封装外层数据,只保留data)return Promise.resolve(result.data)
}, error => {/*后台返回5xx进入该函数内。*/const data = error.response.data;console.log('err', error, data) // error 就是后端接口封装的对象// 没有 doNotHandleErrorMessage 属性时,或 doNotHandleErrorMessage=false时 弹出提示信息if (data && data.dataMap && !data.dataMap[DO_NOT_HANDLE_ERROR_MESSAGE]) {Message({message: data.clientMessage,type: 'error',duration: 5 * 1000})}return Promise.reject(error)
})
  1. 定义刷新令牌方法如下:
/*** 刷新令牌* @param token 令牌对象* @param config axios的配置对象*/
async function refreshingToken(token, config) {if (!isRefreshing) {isRefreshing = truereturn new Promise((resolve, reject) => {// 请求刷新令牌refresh(token.refreshToken).then(data => {// 生成token对象const newToken = new Token(data.accessToken, data.refreshToken, data.accessExpires, data.refreshExpires)// 设置token对象LocalStorageUtil.set(TOKEN_LOCAL_STORAGE, newToken)resolve(data)}).catch(data => {// 刷新令牌失败,直接跳转登录界面store.dispatch('user/resetToken')if (!data.dataMap[DO_NOT_HANDLE_ERROR_MESSAGE]) {Message({message: '登录已过期,请重新登录',type: 'error',duration: 5 * 1000})data.dataMap[DO_NOT_HANDLE_ERROR_MESSAGE] = true}Router.push({ path: '/login'})// 取消队列中的请求cancelTokens.forEach(cancel => {cancel().catch()})reject(data)}).finally(() => {// 恢复变量值isRefreshing = false})})} else {// 阻塞当前请求return new Promise((resolve, reject) => {const id = setInterval(() => {if (!isRefreshing) {clearTimeout(id)resolve();}}, 1000);});}
}

总结

在这之前,我最开始选择的是在响应拦截器进行无感刷新令牌的逻辑处理,并且也已经完成了工作,但是我发现在响应拦截器处理会有一个问题,就是用户会丢失一次请求的操作(这里是指跟页面数据渲染)
具体举个例子:
比如当前用户已经很长时间没操作了(10min),这个时候访问令牌已经失效了,当他再次请求时,会使用刷新令牌获取新的请求。如果这个逻辑是写在Axios的响应拦截器里,那么即使在补发请求,也不会恢复用户和页面的交互。比如查询表格数据,虽然请求补发了,但是数据不会自动渲染。

这也是本次花费时间将其进行优化的主要原因!

使用Axios进行无感刷新Token相关推荐

  1. 实现无感刷新token我是这样做的

    大家好,我是漫步,今天来分享一个登录常常遇到的难题,即登录超时时间与安全性的纠结问题. 原文: https://juejin.cn/post/6983582201690456071 前言 最近在做需求 ...

  2. Vue 无感刷新token

    关于无感刷新的理解:  实现token无感刷新对于前端来说是一项非常常用的技术,其本质是为了优化用户体验,当token过期时不需要用户跳回登录页重新登录,而是当token失效时,进行拦截,发送刷新to ...

  3. 实现无感刷新 token 我是这样做的

    原文: https://juejin.cn/post/6983582201690456071 前言 最近在做需求的时候,涉及到登录token,产品提出一个问题:能不能让token过期时间长一点,我频繁 ...

  4. 关于无感刷新Token,我是这样子做的

    本文正在参加「金石计划 . 瓜分6万现金大奖」 什么是JWT JWT是全称是JSON WEB TOKEN,是一个开放标准,用于将各方数据信息作为JSON格式进行对象传递,可以对数据进行可选的数字加密, ...

  5. token过期怎么办 无感刷新token

    (1)可以通过响应拦截器或者全局前置守卫强制跳转登录页 // 全局前置守卫 router.beforeEach((to, from) => {let token = sessionStorage ...

  6. Laravel6通过jwt(tymon/jwt-auth)实现API用户无感刷新TOKEN

    Laravel6通过jwt实现API用户无感刷新TOKEN 1.TOKEN是什么 2.jwt是什么 3.jwt安装&配置 3.1.通过composer安装 3.2.发布配置 3.3.生成加密密 ...

  7. uniapp 实现无感刷新token, 适应大多数项目

    不管你是用taro uni 还是vue-cli 或者 react-cli 刷新token这块一通百通 本质上 都一样 我之前讲了一个是 在响应拦截哪里做token刷新 其实这样做还是不好的,因为这样我 ...

  8. 无感刷新token方法

    通常,对于一些需要记录用户行为的系统,在进行网络请求的时候都会要求传递一下登录的token.不过,为了接口数据的安全,服务器的token一般不会设置太长,根据需要一般是1-7天的样子,token过期后 ...

  9. 前端无感刷新token

    单个接口的刷新token很好解决,难点是多接口并发 首先将token过期后的请求存到一个数组队列中,想办法让这个请求处于等待中,一直等到刷新token后再逐个重试清空请求队列. 那么如何做到让这个请求 ...

最新文章

  1. spring注解--@Bean
  2. anki怎么设置学习计划_打篮球怎么训练弹跳力?NBA经典训练计划值得学习
  3. 各种门锁的内部结构图_双核CUP,电镀真金把手,0.3秒开锁,欧瑞博智能门锁S2评测...
  4. 基于特征的对抗迁移学习论文_lt;EYD与机器学习gt;迁移学习:PTL选择式对抗网络...
  5. linux环境下安装软件 快速,不超时
  6. 4.帧循环(游戏主循环),schedule
  7. ArcGIS中国工具(ArcGISCTools)3.2 安装教程(附安装包下载)
  8. FTP服务器软件 虚拟目录,FTP服务器软件 虚拟目录
  9. 偶然遇到的Java泛型错误,百思不得其解。
  10. [leetcode]5325. 包含所有三种字符的子字符串数目
  11. Delphi的Hint(2)
  12. 【BZOJ5338】[TJOI2018]异或(主席树)
  13. 号外号外:Exchange2010SP2已经发布
  14. 红帽linux系统解压,linux redhat 红帽 centos 压缩 解压缩
  15. 给出a-z,0-9,输出所有的3个字符的组合
  16. 中国式两性关系把外国人搞晕!
  17. 单因子方差分析Python实现(小鸡增肥)
  18. HTML如何设置幻灯片大小和位置,javascript – 动态调整skitter幻灯片图像大小
  19. 浅谈sPLS和sgPLS
  20. 火车头图片储存-火车头采集图片储存插件及教程

热门文章

  1. spring使用Test测试时报错:Singleton bean creation not allowed while singletons of this factory are in destru
  2. 异常检测的N种方法,阿里工程师都盘出来了
  3. 用博奥如何导入单项工程电子表_博奥常见问题处理汇总
  4. 大疆口袋相机美颜怎么设置_大疆口袋云台相机升级,DJI Pocket 2更大、画质更佳,角度更广,配件更足...
  5. 关于测试工程师瓶颈和突围的一个思考
  6. 分布式环境下的解决方案——分布式锁
  7. 浩辰CAD 2021 “芯”升级,更安全,极速更稳定!
  8. c语言何编写自定义函数,C语言菜鸟基础教程之自定义函数
  9. 湖北绝缘监测仪矿业煤炭石油金矿玉矿铁矿铜矿矿井钢厂
  10. 挑选出两个日期中间的指定礼拜几的时间