The VUE front-end implements a senseless refresh of tokens

foreword

To be honest, there is actually nothing to say about this. If there is complexity, it is mainly in the back end.

It is a very common technology for the front-end to realize the token refresh. Its essence is to optimize the user experience. When the token expires, the user does not need to return to the login page to log in again. Instead, when the token expires, it will be intercepted. Send a request to refresh the token to obtain the latest token for overwriting, so that the user does not feel that the token has expired.

token refresh scheme

  • Solution 1 : The backend returns the expiration time, the frontend judges the token expiration time, and calls the interface to refresh the token.
    Disadvantages: The backend needs to provide a token expiration time field; use local time to judge, if the local time is modified, the local time is higher than the server time Slow and interception will fail.

  • Solution 2 : Write a timer and refresh the token interface regularly.
    Disadvantages: Waste of resources, consume performance, not recommended

  • Solution 3 : Intercept in the response interceptor, and after judging that the token returns expired, call the refresh token interface (⭕recommended)

Specific ideas

After the token expires, the interface returns 401.
insert image description here
Refresh: Clear the token, forcefully jump back to the login page, and re-login with a sense to get a new token and replace it locally. The experience is not good
insert image description here

Refresh without perception: use the refresh_token saved during login to call another interface, change back to the new token value, replace it locally, and complete the unfinished request again (the user has no perception). Specific steps: 1. When you log in for the first time, you
will
get There are two tokens, one is the token normally used by the request interface (short expiration time), and the other is the refresh_token specially used for refreshing (the expiration time is generally longer), which are saved when logging in. localStorage.setItem('refresh_token', xxx) localStorage.setItem('token', xxx)
2. In the response interceptor, introduce the api method call to refresh the token for the 401 status code
3. Replace and save the new local token
4. Replace the headers with the new token
5. Axios initiates again For unfinished requests, return the promise object to the page where the request was originally initiated
6. If the refresh_token has also expired, then judge whether it has expired. If it expires, clear the localstorage and jump back to the login page

The back-end data obtained when logging in:
insert image description here
save
insert image description here
refreshToken.js

import request from './request

export function refreshToken() {
    
    
	const resp = request.get('/refresh_token', {
    
    
		headers: {
    
    
			token: `${
      
      refresh_token}`
		},
		__isRefreshToken: true
	})
	// return resp.code === 0 // 等于0表示刷新token成功
}

export function isRefreshRequest(config) {
    
    
	return !!config.__isRefreshToken //两个取反,变成boolean
}

request.js

import axios from 'axios'
import {
    
     refreshToken, isRefreshRequest } form './refreshToken.js'

// 创建axios实例
const service = axios.create({
    
    
  // baseURL: '',// 所有的请求地址前缀部分
  timeout: 25000, // 请求超时时间(毫秒)
  withCredentials: true// 异步请求携带cookie
})

// 请求拦截器
service.interceptors.request.use((config: any) => {
    
    
	...
}, error => {
    
    
	...
})

// 响应拦截器
service.interceptors.response.use((response: any) => {
    
    
	let res = response.data
	if (res.code == '401' && isRefreshRequest(res.config)){
    
     // 如果没有权限且不是刷新token的请求
		// 刷新token
		try {
    
    
			const res = await refreshToken()
			// 保存新的token
			localStorage.setItem('token', res.data.token)
			// 有新token后再重新请求
			response.config.headers.token = localStorage.getItem('token') // 新token
			const resp = await service.request(response.config)
			return resp.data
			// return service(response.config)
		}catch {
    
    
			localStorage.clear() // 清除token
			router.replace('/login') // 跳转到登录页
		}
	}
}, error => {
    
    
	...
	console.log('error', error)
	return Promise.reject(error)
})

Question 1: How to prevent multiple token refreshes

In order to prevent the token from being refreshed multiple times, a variable isRefreshing can be used to control whether the token is being refreshed

request.js

import axios from 'axios'
import {
    
     refreshToken, isRefreshRequest } form './refreshToken.js'

// 创建axios实例
const service = axios.create({
    
    
  // baseURL: '',// 所有的请求地址前缀部分
  timeout: 25000, // 请求超时时间(毫秒)
  withCredentials: true// 异步请求携带cookie
})

// 请求拦截器
service.interceptors.request.use((config: any) => {
    
    
	...
}, error => {
    
    
	...
})

// 响应拦截器
service.interceptors.response.use((response: any) => {
    
    
	let res = response.data
	let isRefreshing = false
	if (res.code == '401' && isRefreshRequest(res.config)){
    
     // 如果没有权限且不是刷新token的请求
		if (!isRefreshing) {
    
    
			isRefreshing = true
			// 刷新token
			try {
    
    
				const res = await refreshToken()
				// 保存新的token
				localStorage.setItem('token', res.data.token)
				// 有新token后再重新请求
				response.config.headers.token = localStorage.getItem('token') // 新token
				const resp = await service.request(response.config)
				return resp.data
				// return service(response.config)
			}catch {
    
    
				localStorage.clear() // 清除token
				router.replace('/login') // 跳转到登录页
			}
			isRefreshing = false
		}
	}
}, error => {
    
    
	...
	console.log('error', error)
	return Promise.reject(error)
})

Question 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 the request in an array queue, and find a way to keep the request waiting until the token is refreshed and then retry to clear the request queue one by one.
So how to make this request pending?
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, so that the Promise is always in the Pending state (that is, resolve is not called). At this time, the request will wait and wait. As long as we do not execute resolve, the request will always be in the queue. wait. When the refresh request interface returns, we call resolve again and try again one by one.

request.js

import axios from 'axios'
import {
    
     refreshToken, isRefreshRequest } form './refreshToken.js'

// 创建axios实例
const service = axios.create({
    
    
  // baseURL: '',// 所有的请求地址前缀部分
  timeout: 25000, // 请求超时时间(毫秒)
  withCredentials: true// 异步请求携带cookie
})

// 请求拦截器
service.interceptors.request.use((config: any) => {
    
    
	...
}, error => {
    
    
	...
})

// 响应拦截器
service.interceptors.response.use((response: any) => {
    
    
	let res = response.data
	let isRefreshing = false
	let requests = [] // 请求队列
	if (res.code == '401' && isRefreshRequest(res.config)){
    
     // 如果没有权限且不是刷新token的请求
		if (!isRefreshing) {
    
    
			isRefreshing = true
			// 刷新token
			try {
    
    
				const res = await refreshToken()
				// 保存新的token
				localStorage.setItem('token', res.data.token)
				// 有新token后再重新请求
				response.config.headers.token = localStorage.getItem('token') // 新token
				
				// token 刷新后将数组的方法重新执行
		        requests.forEach((cb) => cb(token))
		        requests = [] // 重新请求完清空
		        
				const resp = await service.request(response.config)
				return resp.data
				// return service(response.config)
			}catch {
    
    
				localStorage.clear() // 清除token
				router.replace('/login') // 跳转到登录页
			}
			isRefreshing = false
		} else {
    
    
			// 返回未执行 resolve 的 Promise
			return new Promise(resolve => {
    
    
				// 用函数形式将 resolve 存入,等待刷新后再执行
				request.push(token => {
    
    
					response.config.headers.token = `${
      
      token}`
					resolve(service(response.config))
				})
			})
		}
	}
}, error => {
    
    
	...
	console.log('error', error)
	return Promise.reject(error)
})

Specifically, you can learn from this video
token refresh

insert image description here
insert image description here
insert image description here

Guess you like

Origin blog.csdn.net/weixin_44582045/article/details/132539037