Token过期处理
Token用于进行接口鉴权,但是Token具有由后端设置的过期时间,当Token过期以后,就无法再请求数据了
项目中后端设置的过期时间为24h,测试时我们可以手动修改token值让Token失效
处理方式:
方式1:用户重新登录,获得新的Token就可以了,但是当过期时间较短的时候,每次都是要重新登录操作 的,体验很差
为了提高用户的信息安全性,Token的过期时间都比较短(就算万一泄露了,过一会儿也就过期无效化了)
方式2:根据用户信息,自动给用户生成新的Token,减少登录次数
我们观察前面的功能的话,接口的响应信息中是有三个和token相关的信息的
access_token:当前使用的token,用于访问需要授权的接口
expires_in:access_token的过期时间
refresh_token:刷新获取新的access_token
刷新Token 的方法有两种:
方法一:
在每个请求发起前进行拦截,根据expires_in判断token是否过期,如果过期则会刷新后再继续请求接口
优点:请求前拦截处理,能节省请求次数
缺点:后端需要提供Token过期时间字段(例如:expires_in),并且需要结合计算机本地时间判断,如果计算机时间被篡改(特别是比服务器时间满)时,拦截会失败的
方法二:
在每个请求响应后进行拦截,如果发现请求失败(Token过期导致的)时,刷新Token再刷新请求接口
优点:无需Token过期时间字段,无需判断时间
缺点:多消耗一次请求
这里推荐使用方法二,相比较下来,方法二更加的稳定,不会出现意外的问题
Axios响应拦截器与错误处理
响应拦截器会在响应接收完毕,在对应请求处理前被拦截器拦截,响应拦截器参数response中保存了相应的信息
// Axios 官方文档:响应拦截器
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
那么我们接来下将响应拦截器设置到utils/request.js中,将axios更改为创建的request(因为我们使用了ESLint规范,记得去除所有的分号)
error是需要console.dir()输出的
// utils/request.js
...
// 设置响应拦截器
request.interceptors.response.use(function (response) {
// 状态码为 2xx 都会进入这里
console.log('请求响应成功了:', response)
return response
}, function (error) {
// 超出 2xx 都会进入这里
console.dir(error)
return Promise.reject(error)
})
export default request
Axios错误处理
错误处理,需要在拦截器中找到特定的错误情况进行token刷新
当出现错误时,通过Elemnt的Message组件设置提示,这里我们采用的是引入方式操作
引入的Message与之前使用的this.$message是相同的,只是引入方式与操作方式不同
// 通过局部引入的方式,引入Element的Message组件功能
import { Message } from 'element-ui'
// 响应拦截器
request.interceptors.response.use(function (response) {
// 状态码2xx会执行这里
console.log('响应成功了', response)
return response
}, function (error) {
if (error.response) {
// 请求发送成功,响应接收完毕,但是状态码为失败的情况
// 1.判断失败的状态码情况(主要处理401的情况)
const { status } = error.response
let errorMessage = ''
if (status === 400) {
errorMessage = '请求参数错误'
} else if (status === 401) {
// 2.Token无效(过期)处理
errorMessage = 'Token 无效'
} else if (status === 403) {
errorMessage = '没有权限,请联系管理员'
} else if (status === 404) {
errorMessage = '请求资源不存在'
} else if (status >= 500) {
errorMessage = '服务器错误,请联系管理员'
}
Message.error(errorMessage)
} else if (error.request) {
// 请求发送成功,未收到响应
Message.error('请求超时请重试')
} else {
// 意料之外的错误
Message.error(error.message)
}
// 将本次请求的错误对象继续向后抛出,让接收响应的处理函数进行操作
return Promise.reject(error)
})
刷新Token
HTTP 状态码401表示未授权,导致401的情况有:
没有Token
Token无效
Token过期
判断方法:
检测是否存在refresh_token:(后端通常会限制每个refresh_token只能获取一次新的Token)
如果有,那就通过refresh_token获取新的access_token
获取成功,重启发送请求,请求接口数据就行
获取失败,跳转登录页
如果没有,跳转登录页
由于要进行跳转,在utils/request.js中引入router/index.js
// utils/request.js
// 引入 router
import router from '@/router'
首先要检测store是否有user信息(有就证明是正常登陆,一定存在的有refresh_token),如果存在的有refresh_token的话就请求新的access_token,需要用到对应的刷新接口,接下来检查是否有新的access_token
失败的话,清除用户信息,跳转登录页
跳转登录操作与之前是一致的,建议封装起来
成功的话,更新access_token,同时重新请求之前401的接口
// utils/
...
// 封装跳转登录页面的函数
function redirectLogin () {
router.push({
name: 'login',
query: {
// router.currentRoute 用于获取当前路由对应的路由信息对象
redirect: router.currentRoute.fullPath
}
})
}
// 设置响应拦截器
request.interceptors.response.use(function (response) {
...
}, function (error) {
// 超出 2xx 都会进入这里
if (error.response) {
...
} else if (status === 401) {
if (!store.state.user) {
/* router.push({
name: 'login',
query: {
// router.currentRoute 用于获取当前路由对应的路由
redirect: router.currentRoute.fullPath
}
}) */
// 封装函数后更改为调用
redirectLogin()
// 阻止后续操作,向下抛出错误对象
return Promise.reject(error)
}
...
}).then(res => {
if (res.data.state !== 1) {
// 清除已经无效的用户信息
store.commit('setUser', null)
// 跳转登录页
/* router.push({
name: 'login',
query: {
// router.currentRoute 用于获取当前路由对应的路由
redirect: router.currentRoute.fullPath
}
}) */
// 封装函数后更改为调用
redirectLogin()
// 阻止后续操作,向下抛出错误对象
return Promise.reject(error)
}
...
}).catch(() => {
store.commit('setUser', null)
/* router.push({
name: 'login',
query: {
// router.currentRoute 用于获取当前路由对应的路由
redirect: router.currentRoute.fullPath
}
}) */
// 封装函数后更改为调用
redirectLogin()
return Promise.reject(error)
})
} else if (status === 403) {
...
处理Token重复刷新
如果页面中存在多个请求(大多数页面中都不会只有一次请求),如果Token过期,每个请求都会刷新Token,这个时候刷新多次都没有意义,又增加了请求个数,还会出现额外的问题

通过浏览器的开发者工具观察,有两次的刷新Token请求,由于两次的刷新token携带的refresh_token相同,会导致一次成功一次失败,失败的那一次会导致页面跳转请求页

为了避免多次请求刷新Token,可以通过一个变量isRefreshing标记Token的刷新状态
默认状态为false,并且在发送刷新Token请求前检测,状态是false才能发送
发送刷新请求的时候,设置标记为true
请求完毕,设置为false
// layout/components/app-header.vue
...
// 是否正在更新 Token
let isRefreshing = false
request.interceptors.response.use(function (response) {
...
} else if (status === 401) {
if (!store.state.user) {...}
// 发送刷新请求前判断 isRefreshing 是否存在其他已发送的刷新请求
// 1 如果有,则将当前请求挂起,等到 Token 刷新完毕再重发,这里先设置为 return
if (isRefreshing) {
return
}
// 2. 如果没有,则更新 isRefreshing 并发送请求,继续执行后续操作
isRefreshing = true
// 发送刷新请求
return request({
...
}).then(res => {
...
}).catch(() => {
...
}).finally(() => {
// 3 请求完毕,无论成功失败,设置 isRefreshing 为 false
isRefreshing = false
})
} else if (status === 403) {
...
虽然刷新Token的问题解决了,但是之前发送的两个请求只有一个成功执行,其他的请求都被阻止了
如何解决?
我们声明一个数组存储所有被挂起的请求,当Token刷新完毕再将这些请求重新发送
// 存储是否正在更新token 的状态
let isRefreshing = false
// 存储因为token刷新而挂起的请求
let requests = []
// 响应拦截器
request.interceptors.response.use(function (response) {
// 状态码2xx会执行这里
console.log('响应成功了', response)
return response
}, function (error) {
if (error.response) {
// 请求发送成功,响应接收完毕,但是状态码为失败的情况
// 1.判断失败的状态码情况(主要处理401的情况)
const { status } = error.response
let errorMessage = ''
if (status === 400) {
errorMessage = '请求参数错误'
} else if (status === 401) {
// 2.Token无效(过期)处理
// 第一,无token信息
if (!store.state.user) {
redirectLogin()
return Promise.reject(error)
}
// 检测是否已经存在了正在刷新token的请求
if (isRefreshing) {
// 将当前失败的请求存起来,存储到请求列表中
return requests.push(() => {
// 当前函数调用后,会自动发送本次失败请求
request(error.config)
})
}
isRefreshing = true
// 第二,Token无效(错误Token,过期Token)
// 发送请求,获取新的access_token
return request({
method: 'POST',
url: '/front/user/refresh_token',
data: qs.stringify({
refreshtoken: store.state.user.refresh_token
})
}).then(res => {
// -刷新token失败
if (res.data.state !== 1) {
// 清除无效的用户信息
store.commit('setUser', null)
// 封装重复的跳转登录操作
redirectLogin()
return Promise.reject(error)
}
// 刷新token成功
// 存储新的token
store.commit('setUser', res.data.content)
// 重新发送失败的请求
// 根据reques
// 发送多次失败的请求
requests.forEach(callback => callback())
// 发送完毕清除requests 内容即可
requests = []
// 将本次请求发送
return request(error.config)
}).catch(err => {
console.log(err)
}).finally(() => {
// 无论成功还是失败都会执行
// 请求发送完毕,响应处理完毕,刷新状态更改为false就行了
isRefreshing = false
})
解决
Token过期处理的更多相关文章
- vue axios封装以及登录token过期跳转问题
Axios配置JWT/封装插件/发送表单数据 首先请务必已仔细阅读 Axios 文档并熟悉 JWT: 中文文档 JWT 中文文档 安装 npm install axios npm install es ...
- vue中前端处理token过期的方法与axios请求拦截处理
在处理token过期的这个问题上困扰了我很久,现在终于解决的了,所以分享出来给大家,希望能够对大家有所帮助. 首先,当然是路由进行拦截,路由拦截当然是在beforeEach中了: router.bef ...
- token回话保持,axios请求拦截和导航守卫以及token过期处理
1:了解token:有时候大家又说token令牌.整个机制是前端第一次登陆发送请求,后端会根据前端的用户名和密码, 通过一些列的算法的到一个token令牌, 这个令牌是独一无二的,前端每次发送请求都需 ...
- Retrofit Token过期 重新请求Token再去请求接口
需求是这样的:请求接口A -- 服务器返回数据Token过期或失效 -- 重新请求Token并设置 -- 再去请求接口A 刚解决了这个问题,趁热打铁,写个博客记录一下:这个Token是添加到请求头里 ...
- 如何解决前后端token过期问题
问题描述: 首先后端生成的token是有时限的,在一段时间后不管前端用户是否进行了访问后端的操作,后端的token都会过期,在拦截器阶段就会返回错误的请求:token过期,从而拿不到想要的请求数据. ...
- 如何管理第三方接口token过期时间
背景: 随着微服务的盛行,做开发时不可避免的要涉及第三方接口,安全起见,这些接口都会需要一个token参数.而token一般都会有一个过期时间,比如2小时或者30分钟.那么如何在自己的应用中存储并管理 ...
- axios reponse请求拦截以及token过期跳转问题
前两天项目中遇到了token拦截,需要在请求的header头里放置token,需要用到response拦截,调试过程中遇到了拿不到token的问题 我用的axios实例 let token = sto ...
- k8s中token过期重新生成
k8s中token过期重新生成 通过kubeadm初始化之后,都会提供node加入的token 默认的token的有效期是24小时,当过期了,如何新生成呢 重新生成token: [root@k8s-m ...
- Laravel Passport token过期后判断refresh_token是否过期
需求:前后端分离状态下,登录失效(token过期)后,前端需要知道下一步是跳转到登录页面还是使用refresh_token刷新token. 这就需要后端根据是否可以刷新token(refresh_to ...
- 微信access token过期
两台服务器使用同一个微信账号(同一个app id) 时,当其中一台服务器向微信请求access token时,会造成另一台服务器的access token过期
随机推荐
- go iris框架文件上传下载
在 Iris 框架中,可以使用内置的 iris 包中的 Context 对象来处理文件上传和下载.以下是一个简单的示例代码: package main import ( "github.co ...
- APP稳定性测试Monkey工具介绍
一.Monkey工具简介 1.monkey的来源: Monkey是一个命令行工具,使用安卓调试桥(adb)来运行它,模拟用户:触摸屏幕.滑动Trackball.按键等随机事件流来对设备上的程序进行压力 ...
- myJRebel 已不可用
周末在家里撸代码,突然 IDEA 提示 JRebel 需要激活. 原来一直使用的 myJRebel 的激活码,天真的以为是我的网络问题,尝试重新激活,结果不管用,就想去 myJrebel 的网站上去看 ...
- 32位机转化11位手机号以及BLE与USB的切换
目录 用现有的资源,去实现本应该用更多资源来实现的需求,是一件很有意思的事情.不是说提倡这样使用,而是换一种思路解决问题比较新奇,或是在很多限制既定的情况下可以应急. 比如说,582m芯片,默认用32 ...
- [Vue warn]: Do not use built-in or reserved HTML elements as component id: text 错误的解决办法
引入的text 没用,要么注释掉,要么引用
- Java-如何打包下载成.zip文件
打包下载成.zip文件 项目背景 公司使用vue + SpringBoot实现批量下载功能 今天在调试批量下载这个功能.打包成.zip文件时,在返回给前端浏览器出现报错信息: 后端报错: ERROR ...
- mongodb-命令小结
小白暂时在这里记录些涉及到的 在某条记录添加一个字段: 先查询_id: db.subscriptions.find({"channel":"parking.notify. ...
- urllib编码问题踩坑
今天在使用python的urllib对网页进行爬取的时候,中文一直出现错误 UnicodeEncodeError: 'ascii' codec can't encode character '\u62 ...
- nvm node 版本管理
nvm安装与使用 1.nvm是什么 nvm全名node.js version management,顾名思义是一个nodejs的版本管理工具.通过它可以安装和切换不同版本的nodejs.下面列出下载. ...
- undefined reference to symbol xxxxx和undefined symbol:xxxx错误的原因分析以及解决方法
Linux下编译程序时,经常会遇到"undefined reference to XXX" 报错,或者运行时出现undefined symbol:xxxx报错. 这里总结一些可能的 ...