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
126
127
128
129
130
131
132
133
134
135
// 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对象
// 在这里修改你的BaseURL
const axiosInstance = axios.create({
baseURL: '/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 状态
*
* 其中对于error.config.url的判断是不包含上文设置的baseURL的
*/
if (
(error.response.status.toString() === '401' ||
error.response.status.toString() === '403') &&
!(error.config.url === 'token/') &&
!(error.config.url === '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.href = window.location.origin
})
.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 loginByPassword(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,
loginByPassword,
setRefreshToken,
setAccessToken,
getRefreshToken,
removeToken,
}

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