我的应用程序使用
JWT Token
与后端进行身份验证。我有一个有角度的 Interceptor
可以捕获 http 调用中的任何错误。此拦截器检查调用是否返回 401 Unauthorized
响应,如果是,它会调用端点以刷新令牌,然后再次重试原始调用。
我的问题是,由于某种原因,对refreshToken端点的调用被执行了两次。
这是我的拦截器(为简洁起见进行了编辑):
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: any) => {
switch (error.status) {
// unauthorized
case 401:
// the token is stored in an NGRX store, so retrieve it first
return this.userStore.pipe(select(fromUser.getUserAccessToken)).pipe(
take(1),
mergeMap(userToken => {
if (userToken) {
// token exists but may be invalid, try to refresh
return this.userService.refreshToken(userToken).pipe(
switchMap(refreshedToken => {
if (refreshedToken) {
...
} else {
// token was not refreshed, delete session
...
}
})
);
}
})
);
}
})
);
}
return this.userService.refreshToken(userToken)
是执行两次的语句(使用相同的原始令牌),因此第一次调用刷新将执行,第二次调用显然会失败,因为它试图再次刷新相同的令牌,从而导致用户会话被删除。
有什么想法吗?
更新: 我仍在挖掘这一点。我在代码中放置了一堆断点和
console.log
:
catchError((error: any) => {
>>>>> console.log('1');
switch (error.status) {
case 401:
return this.userStore.pipe(select(fromUser.getUserAccessToken)).pipe(
take(1),
mergeMap(userToken => {
>>>>> console.log('2');
if (userToken) {
return this.userService.refreshToken(userToken).pipe(
switchMap(refreshedToken => {
>>>>> console.log('3');
Console.log
#1 和 #2 仅打印到控制台一次,这意味着 http error
只被捕获一次。但是 #3 被打印了两次,我认为这意味着要么调用刷新端点两次,要么可观察以某种方式返回两个值?
不知道为什么,但似乎
refreshToken
输出多个值。这可以通过 take
运算符轻松修复。像这样的东西:
return this.userService.refreshToken(userToken).pipe(
take(1),
switchMap(refreshedToken => {
>>>>> console.log('3');
您的后端调用可能是一个 OPTIONS 请求
预检请求
与简单请求(上面讨论的)不同,“预检”请求首先将 HTTP OPTIONS 请求标头发送到其他域上的资源,以确定实际请求是否可以安全发送。跨站点请求会像这样进行预检,因为它们可能会对用户数据产生影响。特别是,在以下情况下,请求会被预检:
它使用 GET 或 POST 以外的方法。另外,如果 POST 用于发送 Content-Type 不是 application/x-www-form-urlencoded、multipart/form-data 或 text/plain 的请求数据,例如如果 POST 请求使用 application/xml 或 text/xml 将 XML 有效负载发送到服务器,则该请求将被预检。 它在请求中设置自定义标头(例如,请求使用 X-PINGOTHER 等标头)
首先,您确实需要提供一个 StackBlitz 或与该错误类似的东西,以便我们可以帮助诊断此问题。话虽如此,我对此很好奇,因为我以前遇到过这样的问题,所以我做了一些研究。我发现的大多数示例都表明,
refreshToken
方法返回的Observable是共享的,以防止多个订阅。在这个答案中https://stackoverflow.com/a/52720791/5799931你可以看到this.authService.refreshToken()
Observable是共享的。我会尝试这个
return this.userService.refreshToken(userToken).pipe(
share(), <----- ADD THE SHARE HERE
switchMap(refreshedToken => {
if (refreshedToken) {
...
} else {
// token was not refreshed, delete session
...
}
})
);
您还可以在
refreshToken
方法中共享 HttpClient 返回的 Observable。
也许我回答晚了,但是为什么拦截器会获取两次是因为拦截器会检查 HTTPEvent 的事件..
export function testInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
return next(req).pipe(
tap((event) => {
console.log(event.type, 'Test Interceptor', req.url);
})
);
}
查看 event.type。请参阅此处文档。