When it comes to the login token, the product asks a question: can I make the token expire a little longer? I have to log in frequently.
Front end: back end, can you set the token expiration time longer.
Back end: Yes, but that's not safe. You can use a better method.
Front end: what method?
Back end: give you the interface to refresh the token, and refresh the token regularly
Front end: OK, let me think about it
**
demand
**
When the token expires, refresh the token. The front end needs to refresh the token without feeling, that is, when brushing the token, the user should be unaware and avoid frequent login. Realization idea
Method 1
The back end returns the expiration time. The front end judges the expiration time of the token and calls the refresh token interface
Disadvantages: the backend needs to provide an additional token expiration time field; The 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 2
Write a timer and refresh the token interface regularly
Disadvantages: it wastes resources and consumes performance. It is not recommended.
Method 3
In response to interceptor intercepting, it is called to refresh the token interface after judging that token returns to expire.
realization
The basic skeleton of axios, using service interceptors. Response to intercept
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) } )
Problem solving
Question 1: how to prevent multiple refresh of token s
We use a variable isRefreshing to control whether the state 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) } )
Question 2: how can other interfaces solve two or more requests at the same time
When the second expired request comes in and the token is being refreshed, we first save the request to an array queue and try to keep the request waiting until the token is refreshed, and then try to empty the request queue one by one. So how do you keep this request waiting? In order to solve this problem, we have to use Promise. After the request is stored in the queue, a Promise is returned at the same time. Keep the Promise in the Pending state (that is, do not call resolve). At this time, the request will wait and wait. As long as we do not execute resolve, the request will wait all the time. When the interface of the refresh request returns, we call resolve and try again one by one. Final code:
import axios from 'axios' // Are you refreshing your tags let isRefreshing = false //Retry Queue let requests = [] service.interceptors.response.use( response => { //Contract code 409 token expired if (response.data.code === 409) { if (!isRefreshing) { isRefreshing = true //Call the interface to refresh the token return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: getToken() }).then(res => { const { token } = res.data // Replace token setToken(token) response.headers.Authorization = `${token}` }).catch(err => { //Skip to login page removeToken() router.push('/login') return Promise.reject(err) }).finally(() => { isRefreshing = false }) } else { // Return Promise without resolve return new Promise(resolve => { // Store resolve as a function and wait for the refresh before executing 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) } )
The product requirements are completed. I don't know if there is a better solution.