demand

There is a requirement recently: after the front end logs in, the back end returns a token and refresh_token. When the token expires, the old refresh_token is used to obtain a new token. The front end needs to refresh the token painlessly, that is, the user must not be aware of the request to refresh the token.

Demand analysis

When a user sends a request, the system checks whether the token has expired. If the token has expired, the refreshToken interface is adjusted first. After obtaining a new token, the system continues the request.

The difficulty of this problem is: when multiple requests are made at the same time, but the interface that refreshed the token has not returned, what should be done with the other requests? We will share the process step by step.

implementation

Here will use axios to implement, the above method is request to intercept, so will use axios. Interceptors. Response. Use () method.

First of all, the token in the project exists in localStorage.

If the refreshToken interface has not returned yet and another expired request comes in, the above code will execute refreshToken again. This will cause the refreshToken interface to be executed multiple times, so you need to prevent this problem. We can use a flag in request.js to mark whether the token is being refreshed. If the token is being refreshed, the interface for refreshing the token is no longer called.

This will avoid entering the method when the token is refreshed. If two requests are made at the same time, the first one must enter refreshToken and try again, while the second one is discarded and still fails, so you need to resolve the retry issue of the other interfaces.

When two or more requests are initiated at the same time, how can other interfaces retry? Two interfaces almost initiate and return at the same time. The first interface will enter the process of retrying after refreshing the token, while the second interface needs to save the request first and retry after refreshing the token. Similarly, if three requests are sent at the same time, cache the last two interfaces and try again after the token is refreshed. Because the interfaces are asynchronous, this can be a bit of a hassle.

When the second expired request comes in and the token is being refreshed, we first store the request in an array queue and try to keep the request waiting until the token is refreshed and then try to clear the request queue one by one. So how do you keep the request waiting? To solve this problem, we had to turn to Promise. We queued the request and returned a Promise, leaving the Promise in the Pending state. The request will wait and wait as long as we do not call resolve. When the refreshed interface returns, we call resolve again and try again one by one. Final code:

import axios from 'axios'
import { Loading, Message, MessageBox } from 'element-ui'
import api from './api'
import { getToken, setToken, removeToken, getRefreshToken } from '.. /utils/cookies'

let UserModule = {
	RefreshToken: (data) => {
		setToken('Bearer '+ data.access_token, data.refresh_token)}} // Specifies whether the token is being refreshedlet isRefreshing = false// Retry queue, each entry will be a function form to be executedlet retryRequests = []

const request = axios.create({
	baseURL: api.baseUrl,
	timeout: 50000,
	withCredentials: trueNecessary}) / / / / cookie cross-domain HTTP request interceptor request request. The interceptors. Request. Use ((config) = > {if (getToken()) {
			config.headers['Authorization'] = getToken()
		}
		returnconfig }, (error) = > {Promise. Reject (error)}) / / HTTP response interceptor response request. The interceptors. Response. Use ((response) = > { // code == 0: const res = response.dataif(res.code ! = = 0) {if (res.message) {
				Message({
					message: res.message,
					type: 'error',
					duration: 5 * 1000
				})
			}
			return Promise.reject(res)
		} else {
			return response.data
		}
	},
	(error) => {
		if(! error.response)returnPromise.reject(error) // Re-obtain the token according to refreshToken // 5000 System busy // 5001 parameter error // 1003 The user does not have sufficient permission to access the resource interface // 1004 Full authentication is required to access the resource // 1001Access_token is invalid // 1002Refresh_token is invalidif (error.response.data.code === 1004 || error.response.data.code === 1001) {
			const config = error.config
			if(! isRefreshing) { isRefreshing =true
				returnGetRefreshTokenFunc ().then((res) => {// Reset token userModule.refreshToken (res.data.data) config.headers['Authorization'] = getToken() // Has refreshed the token, // @ts-ignore retryRequests. ForEach ((cb) => cb(getToken())) // Clear the queue retryRequests = [] // BaseURL is not needed here because the URL will be requested again, and the URL already contains the baseURL part config.baseurl =' '
						return request(config)
					})
					.catch(() => {
						resetLogin()
					})
					.finally(() => {
						isRefreshing = false})}else{// Refreshing the token to return a promise that has not yet been resolvedreturnNew Promise(((resolve)) => {// put the resolve in the queue and save it as a function. // @ts-ignore retryRequests. Push ((token: any) => { config.baseURL =' '
						config.headers['Authorization'] = token
						resolve(request(config))
					})
				})
			}
		} else if (error.response.data.code === 1002) {
			resetLogin()
		} else {
			Message({
				message: error.response.data.message,
				type: 'error',
				duration: 5 * 1000
			})
			returnPromise.reject(error)}}) // Refresh the token's request methodfunction getRefreshTokenFunc() {
	let params = {
		refresh_token: getRefreshToken() || ' '
	}
	return axios.post(api.baseUrl + 'auth-center/auth/refresh_token', params)
}
function resetLogin(title = 'Authentication failed, please log in again! ') {
	if (window.location.href.indexOf('/login') === -1) {
		MessageBox.confirm(title, 'exit', {
			confirmButtonText: 'Log back in',
			cancelButtonText: 'cancel'.type: 'warning'}).then(() => {removeToken() location.reload() // To prevent bugs from Viee-router})}} /** * [] request * @param params * @param operation Interface */function customRequest(url: string, method: any, data: any) {
	// service.defaults.headers['Content-Type']=contentType
	let datatype = method.toLocaleLowerCase() === 'get' ? 'params' : 'data'
	return request({
		url: url,
		method: method,
		[datatype]: data
	})
}

export { request, customRequest }
Copy the code