momo's Blog.

JWT Token过期的时候自动刷新

字数统计: 740阅读时长: 3 min
2021/05/27 Share

前言

在使用JWT进行认证的时候, 后端返回的Token一般为两个.

  • access Token
  • refresh Token

其中access Token 过期时间很快, 默认只有5分钟, 而 refresh Token 则比较长. 所以,当Token过期的时候, 我们可以用refresh Token 去获取最新的 access Token.

思路很简单, 我们只需要在 响应拦截器中去判断Token过期的代码,随后在请求最新的Token,并且拿到新Token以后, 我们重新在请求一遍源请求,返回不就行了吗?

自动判断Token过期,并且刷新

思路

让我们在理清一下步骤

  1. 如果响应拦截器中获取Token过期的状态
  2. refresh Token去请求刷新Token的接口
  3. 拿到最新的Token重新请求接口
  4. 返回数据

代码我就不放了, 但是实现以后发现了一个小问题. 如果在Token过期的时候,同时发出来很多个请求, 那么refresh Token 也会被刷新多次, 那这样是完全没必要的。

观察者模式

上面的逻辑实现起来没什么问题, 但是refresh Token 也会被刷新多次, 有没有方法可以解决这个呢?

  1. 场景:当token失效时,多个请求同时发送请求。

  2. 问题:token会被多次刷新,除了第一次判断token失效后,进行刷新token的操作,其余的刷新token都是多余的。

  3. 解决:采用observer模式,请求为subject,token刷新为observer。监听token刷新后,通知收集到的每一个请求。多个请求中的第一个请求发送时,发现token失效,将当前请求以及后续的请求入队到队列,当token刷新后,将队列里的请求按顺序执行。

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
service.interceptors.response.use(
async response => {
const res = response.data

if (res.code === 50008) {
// Token 已经过期
console.log(isRefreshing)
if (!isRefreshing) {
isRefreshing = true
// 等待store.dispatch('user/getToken')调用成功
const token = await store.dispatch('user/getToken')
if (token) {
// 依次执行队列中的resolve
requestsQueue.forEach(rqs => rqs())
}
requestsQueue = []
// eslint-disable-next-line
isRefreshing = false
}

// 返回一个Promise, 并且将请求配置封装到 resolve 中。
return new Promise(resolve => {
requestsQueue.push(() => { resolve(service(response.config)) })
})
}

// if the custom code is not 200, it is judged as an error.
if (res.code !== 200 && res.code !== 50008) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})

// 50008: Token expired; 50012: Other clients logged in; 50014: Illegal token;
if (res.code === 50012 || res.code === 50014) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)

参考文档

CATALOG
  1. 1. 前言
  2. 2. 自动判断Token过期,并且刷新
    1. 2.1. 思路
    2. 2.2. 观察者模式
  3. 3. 参考文档