Generally, for some systems that need to record user behaviors, they will be required to pass the login token when making network requests. However, for the security of interface data, the server's token is generally not set too long, and it is usually 1-7 days as needed. After the token expires, you need to log in again. However, frequent login will cause a bad experience. Therefore, if you need a good experience, you need to refresh the token regularly and replace the previous token.
To achieve a senseless refresh of the token, there are three main solutions:
Option One:
The back end returns the expiration time, and the front end judges the expiration time of the token every time it requests. If the expiration time is almost reached, it calls the refresh token interface.
Disadvantages : The backend needs to provide an additional token expiration time field; local time judgment is used. If the local time is tampered with, especially when the local time is slower than the server time, the interception will fail.
Method Two
Write a timer, and then refresh the token interface regularly.
Disadvantages : Waste resources, consume performance, not recommended.
Method Three
Intercept in the request response interceptor, after judging that the token return expires, call the refresh token interface.
Combining the above three methods, the best is the third one, because it does not require additional resources. Next, let's look at using axios to make network requests, and then respond to the interception of service.interceptors.response.
import axios from 'axios'
service.interceptors.response.use(
response => {
if (response.data.code === 409) {
return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: getToken() }).then(res => {
const { token } = res.data
setToken(token)
response.headers.Authorization = `${token}`
}).catch(err => {
removeToken()
router.push('/login')
return Promise.reject(err)
})
}
return response && response.data
},
(error) => {
Message.error(error.response.data.msg)
return Promise.reject(error)
}
)
Question 1: How to prevent the token from being refreshed multiple times
In order to prevent the token from being refreshed multiple times, a variable isRefreshing can be used to control whether the status of the token is being refreshed.
import axios from 'axios'
service.interceptors.response.use(
response => {
if (response.data.code === 409) {
if (!isRefreshing) {
isRefreshing = true
return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: getToken() }).then(res => {
const { token } = res.data
setToken(token)
response.headers.Authorization = `${token}`
}).catch(err => {
removeToken()
router.push('/login')
return Promise.reject(err)
}).finally(() => {
isRefreshing = false
})
}
}
return response && response.data
},
(error) => {
Message.error(error.response.data.msg)
return Promise.reject(error)
}
)
2. How to refresh the token when two or more requests are initiated at the same time
When the second expired request comes in and the token is being refreshed, we first store this request in an array queue, find a way to keep this request waiting, and wait until the token is refreshed and then try to clear the request queue one by one.
So how do you make this request awaiting? In order to solve this problem, we have to resort to Promise. After the request is stored in the queue, a Promise is returned at the same time, so that the Promise is always in the Pending state (that is, the resolve is not called). At this time, the request will wait and wait. As long as we do not execute the resolve, the request will always be wait. When the interface of the refresh request comes back, we call resolve and try again one by one.
import axios from 'axios'
// 是否正在刷新的标记
let isRefreshing = false
//重试队列
let requests = []
service.interceptors.response.use(
response => {
//约定code 409 token 过期
if (response.data.code === 409) {
if (!isRefreshing) {
isRefreshing = true
//调用刷新token的接口
return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: getToken() }).then(res => {
const { token } = res.data
// 替换token
setToken(token)
response.headers.Authorization = `${token}`
// token 刷新后将数组的方法重新执行
requests.forEach((cb) => cb(token))
requests = [] // 重新请求完清空
return service(response.config)
}).catch(err => {
//跳到登录页
removeToken()
router.push('/login')
return Promise.reject(err)
}).finally(() => {
isRefreshing = false
})
} else {
// 返回未执行 resolve 的 Promise
return new Promise(resolve => {
// 用函数形式将 resolve 存入,等待刷新后再执行
requests.push(token => {
response.headers.Authorization = `${token}`
resolve(service(response.config))
})
})
}
}
return response && response.data
},
(error) => {
Message.error(error.response.data.msg)
return Promise.reject(error)
}
)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。