如果访问令牌过期,我有一个拦截器来捕获 401 错误。如果它过期,它会尝试刷新令牌以获取新的访问令牌。如果在此期间进行了任何其他调用,它们将排队等待访问令牌得到验证。
这一切都很好。然而,当使用 Axios(originalRequest) 处理队列时,最初附加的 Promise 不会被调用。请参阅下面的示例。
工作拦截器代码:
Axios.interceptors.response.use(
response => response,
(error) => {
const status = error.response ? error.response.status : null
const originalRequest = error.config
if (status === 401) {
if (!store.state.auth.isRefreshing) {
store.dispatch('auth/refresh')
}
const retryOrigReq = store.dispatch('auth/subscribe', token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token
Axios(originalRequest)
})
return retryOrigReq
} else {
return Promise.reject(error)
}
}
)
刷新方法(使用刷新令牌获取新的访问令牌)
refresh ({ commit }) {
commit(types.REFRESHING, true)
Vue.$http.post('/login/refresh', {
refresh_token: store.getters['auth/refreshToken']
}).then(response => {
if (response.status === 401) {
store.dispatch('auth/reset')
store.dispatch('app/error', 'You have been logged out.')
} else {
commit(types.AUTH, {
access_token: response.data.access_token,
refresh_token: response.data.refresh_token
})
store.dispatch('auth/refreshed', response.data.access_token)
}
}).catch(() => {
store.dispatch('auth/reset')
store.dispatch('app/error', 'You have been logged out.')
})
},
auth/actions 模块中的订阅方法:
subscribe ({ commit }, request) {
commit(types.SUBSCRIBEREFRESH, request)
return request
},
以及突变:
[SUBSCRIBEREFRESH] (state, request) {
state.refreshSubscribers.push(request)
},
这是一个示例操作:
Vue.$http.get('/users/' + rootState.auth.user.id + '/tasks').then(response => {
if (response && response.data) {
commit(types.NOTIFICATIONS, response.data || [])
}
})
如果将此请求添加到队列中,因为刷新令牌必须访问新令牌,我想附加原始 then():
const retryOrigReq = store.dispatch('auth/subscribe', token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token
// I would like to attache the original .then() as it contained critical functions to be called after the request was completed. Usually mutating a store etc...
Axios(originalRequest).then(//if then present attache here)
})
一旦访问令牌被刷新,请求队列就会被处理:
refreshed ({ commit }, token) {
commit(types.REFRESHING, false)
store.state.auth.refreshSubscribers.map(cb => cb(token))
commit(types.CLEARSUBSCRIBERS)
},
原文由 Tim Wickstrom 发布,翻译遵循 CC BY-SA 4.0 许可协议
2019 年 2 月 13 日更新
由于许多人对这个主题表现出兴趣,我创建了 axios-auth-refresh 包,它应该可以帮助您实现此处指定的行为。
这里的关键是返回正确的 Promise 对象,所以你可以使用
.then()
进行链接。为此,我们可以使用 Vuex 的状态。如果发生刷新调用,我们不仅可以将refreshing
状态设置为true
,还可以将刷新调用设置为待处理的调用。这样使用.then()
将始终绑定到正确的 Promise 对象,并在 Promise 完成时执行。这样做将确保您不需要额外的队列来保留等待令牌刷新的调用。这将始终将已创建的请求作为 Promise 返回,或者创建新的请求并将其保存以供其他调用使用。现在您的拦截器将类似于以下拦截器。
这将允许您再次执行所有待处理的请求。但是一下子,没有任何询问。
如果您希望挂起的请求按照它们实际调用的顺序执行,您需要将回调作为第二个参数传递给
refreshToken()
函数,就像这样。和拦截器:
我没有测试第二个例子,但它应该可以工作,或者至少给你一个想法。
第一个示例的工作演示- 由于用于它们的模拟请求和服务的演示版本,一段时间后它将无法工作,但代码仍然存在。
来源: 拦截器 - 如何防止被拦截的消息解析为错误