JWT下无感刷新token

实现目标

  • access_token失效时自动根据refresh_token获取新的access_token
  • 自动缓存在刷新access_token时发送的请求,并在获取到access_token后继续/重新请求
  • 避免重复刷新
  • access_tokenrefresh_token同时失效时终止所有操作

代码实现

参考于:[axios无感知刷新token-卑微幻想家’s Blog]

相较于原文:

  • 单个文件完整
  • 使用TypeScript
  • access_tokenrefresh_token同时失效时终止所有操作

注意

  • axiosInstance对象的实例化需要按照真实情况填写
  • login函数需要根据真实情况修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// auth.ts
import axios from 'axios'
let isRefreshing = false // 是否正在刷新token

// 失效后同时发送请求的容器 -- 缓存接口
let callbacks: Function[] = []
function onAccessTokenFetched(newToken: String) {
callbacks.forEach((callback) => {
callback(newToken)
})
// 清空缓存接口
callbacks = []
}
// 添加缓存接口
function addCallbacks(callback: Function) {
callbacks.push(callback)
}

// 实例化axios对象
const axiosInstance = axios.create({
baseURL: 'http://127.0.0.1:8000/api/',
headers: {
'Content-Type': 'application/json'
}
})

// 请求拦截器,用于添加请求头
axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem('access_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})

// 响应拦截器
axiosInstance.interceptors.response.use(
(response) => {
return response
},
(error) => {
/**
* 将未授权接口缓存起来。retryOriginalRequest 这个 Promise 函数很关键,它一直处于等待状态。
* 只有当token刷新成功后,onAccessTokenFetched 这个函数执行了回调函数,返回了 resolve 状态
*/
if (
error.response &&
error.response.status === 401 &&
!error.config.url.endsWith('token/') &&
!error.config.url.endsWith('token/refresh/')
) {
// 获取当前的请求
const config = error.response.config
// 刷新token的promise
const retryOriginalRequest = new Promise((resolve) => {
addCallbacks((newToken: String) => {
// 表示用新的token去替换掉原来的token
config.headers.Authorization = `Bearer ${newToken}`
resolve(axiosInstance.request(config)) // 调用resolve请求队列里面接口
})
})
// 无感刷新Token
if (!isRefreshing) {
isRefreshing = true
axiosInstance
.post('/token/refresh/', { refresh: getRefreshToken() })
.then((response) => {
// 用refreshToken获取新的token
const accessToken = response.data.access
setAccessToken(accessToken)
onAccessTokenFetched(accessToken)
})
.catch(() => {
// 刷新token错误跳转到登陆页面
removeToken()
alert('登录无效/过期,请重新登录')
location.reload()
})
.finally(() => {
isRefreshing = false
})
}
return retryOriginalRequest // 将token过期期间请求的接口包装成promise返回,等待刷新token后重新请求
} else {
return Promise.reject(new Error(error.message || 'Error'))
}
}
)

function setRefreshToken(token: string) {
localStorage.setItem('refresh_token', token)
}

function setAccessToken(token: string) {
localStorage.setItem('access_token', token)
}

function getRefreshToken() {
return localStorage.getItem('refresh_token')
}

function removeToken() {
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
}

async function login(username: string, password: string): Promise<boolean> {
try {
const response = await axiosInstance.post('token/', {
username: username,
password: password
})
if (response.status === 200) {
setAccessToken(response.data.access)
setRefreshToken(response.data.refresh)
return true
}
return false
} catch (error) {
console.error(`Error occurred while logging in: ${error}`)
return false
}
}

export { axiosInstance, login, setRefreshToken, setAccessToken, getRefreshToken, removeToken }

JWT下无感刷新token
http://www.coooolfan.com/2024/06/15/Silent-token-refresh-under-JWT/
作者
Coolfan
发布于
2024年6月15日
许可协议