1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 使用Axios进行无感刷新Token

使用Axios进行无感刷新Token

时间:2023-05-17 18:53:55

相关推荐

使用Axios进行无感刷新Token

前言

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

举个例子:

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

实现

令牌格式

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

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

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

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

后端准备

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

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

前端准备

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

首先定义全局变量:

// 按照axios官方提示需要引入这两步(取消axios的请求)const CancelToken = axios.CancelToken;const source = CancelToken.source();// 全局变量,记录是否正在执行刷新令牌任务let isRefreshing = false// 需要取消请求的集合const cancelTokens = []

定义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)})

定义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)})

定义刷新令牌方法如下:

/*** 刷新令牌* @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的响应拦截器里,那么即使在补发请求,也不会恢复用户和页面的交互。比如查询表格数据,虽然请求补发了,但是数据不会自动渲染。

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。
相关阅读
无感刷新token方法

无感刷新token方法

2022-06-05

前端无感刷新token

前端无感刷新token

2023-06-01

vue无感刷新token

vue无感刷新token

2020-02-12

实现双token无感刷新

实现双token无感刷新

2024-03-26