angular 令牌刷新后 http 拦截器不再调用请求
angular http interceptor not calling request again after token refresh
我的项目中有一个 http 拦截器,它处理访问令牌的刷新。
当用户的访问令牌过期时,请求将出现 401 错误,在这种情况下,此函数应处理所有事情,刷新令牌并使用新的访问令牌再次调用请求。
函数调用如下:
return next.handle(request).pipe(catchError((error) => {
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handle401Error(request, next);
} else {
return throwError(error);
}
}));
和 handle401:
handle401Error(request: HttpRequest<any>, next: HttpHandler): any {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
this.auth.refreshAccessToken().then((token: Token) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(token.access_token);
return next.handle(this.addToken(request, token.access_token));
});
} else {
return this.refreshTokenSubject.pipe(
filter((token) => token !== null),
take(1),
switchMap((token) => {
return next.handle(this.addToken(request, token));
}));
}
}
我根据一篇文章创建了拦截器,它应该可以正常工作,令牌刷新非常有效,但是
return next.handle(this.addToken(request, token.access_token));
应该使用现在有效的令牌再次调用请求,但没有调用它。
问题
this.auth.refreshAccessToken()
returns a promise(我假设给出 .then()
)。
说明
以防万一您不熟悉 promises,它们是处理异步代码的通用系统。这是一个link to the docs.
this.auth.refreshAccessToken().then()
将一个函数作为参数,这很常见,您提供了一个匿名箭头函数 (token: Token) => { ... }
。
当您执行 return next.handle(this.addToken(request, token.access_token));
时,您在箭头函数内部,因此您实际上并不是 returning 来自 handle401Error()
的值,而是 returning 一个值 .then()
.
.then()
做 return 一个值,但你现在 return 没有那个值。
您可以在您的 else 块中看到此操作正确完成:
return this.refreshTokenSubject.pipe( <-- top-level return
filter((token) => token !== null),
take(1),
switchMap((token) => {
return next.handle(this.addToken(request, token)); <-- nested return
}));
}
解决方案
TLDR;
return from(this.auth.refreshAccessToken()).pipe(switchMap((token: Token) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(token.access_token);
return next.handle(this.addToken(request, token.access_token));
}));
说明
一件可能会让事情变得更容易的小事,我建议你使用 return 类型的 handle401Error()
而不是 any
作为 return 类型26=] 即 Observable<HttpEvent<any>>
.
你需要做的是 return 从 this.auth.refreshAccessToken().then()
.
里面得到 next.handle()
的值
可能有多种方法可以做到这一点,但我要推荐 Angular/RxJS 风格。
正如我之前所说,promises 就像 observables,RxJS (v6+) 提供了一种将 promise 转换为 observable 的方法,例如:
import { from } from 'rxjs';
const observable = from(promise);
您可以使用它来将 this.auth.refreshAccessToken()
转换为可观察对象:
from(this.auth.refreshAccessToken())
现在我们有了一个 observable,您可能倾向于使用 subscribe
获取值,但这不是您想要做的,因为您的拦截器正在 returning 一个最终的 observable在其他地方订阅。
您可以改为使用 pipe, which allows you to use a number of operators provided by RxJS. In this case, you want to wait for your first observable refreshAccessToken()
to emit and then you want to return next.handle()
. The commonly used operator for this task is switchMap。
您会注意到您的 else 块实际上正在使用这个:
return this.refreshTokenSubject.pipe(
filter((token) => token !== null),
take(1),
switchMap((token) => { <-- switchMap
return next.handle(this.addToken(request, token));
}));
}
switchMap()
等待第一个 observable 发出,然后将值输出到你的回调函数,期待你 return 另一个 observable。在您的情况下,这意味着您将 then()
替换为 pipe(switchMap())
.
如 TLDR 所示:
return from(this.auth.refreshAccessToken()).pipe(switchMap((token: Token) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(token.access_token);
return next.handle(this.addToken(request, token.access_token));
}));
这应该可以解决您的问题,如果这不起作用,请在下方评论。
句柄 handle401Error 函数不应该 return next.handle(...)。无论如何,您已经涵盖了在拦截函数中被 returned 的内容。而是像正常和 return of(null) 一样将令牌添加到请求中;在 handle401Error 函数中。除非您想抛出自定义错误,否则 catchError 函数应始终 return null。
我的项目中有一个 http 拦截器,它处理访问令牌的刷新。
当用户的访问令牌过期时,请求将出现 401 错误,在这种情况下,此函数应处理所有事情,刷新令牌并使用新的访问令牌再次调用请求。
函数调用如下:
return next.handle(request).pipe(catchError((error) => {
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handle401Error(request, next);
} else {
return throwError(error);
}
}));
和 handle401:
handle401Error(request: HttpRequest<any>, next: HttpHandler): any {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
this.auth.refreshAccessToken().then((token: Token) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(token.access_token);
return next.handle(this.addToken(request, token.access_token));
});
} else {
return this.refreshTokenSubject.pipe(
filter((token) => token !== null),
take(1),
switchMap((token) => {
return next.handle(this.addToken(request, token));
}));
}
}
我根据一篇文章创建了拦截器,它应该可以正常工作,令牌刷新非常有效,但是
return next.handle(this.addToken(request, token.access_token));
应该使用现在有效的令牌再次调用请求,但没有调用它。
问题
this.auth.refreshAccessToken()
returns a promise(我假设给出 .then()
)。
说明
以防万一您不熟悉 promises,它们是处理异步代码的通用系统。这是一个link to the docs.
this.auth.refreshAccessToken().then()
将一个函数作为参数,这很常见,您提供了一个匿名箭头函数 (token: Token) => { ... }
。
当您执行 return next.handle(this.addToken(request, token.access_token));
时,您在箭头函数内部,因此您实际上并不是 returning 来自 handle401Error()
的值,而是 returning 一个值 .then()
.
.then()
做 return 一个值,但你现在 return 没有那个值。
您可以在您的 else 块中看到此操作正确完成:
return this.refreshTokenSubject.pipe( <-- top-level return
filter((token) => token !== null),
take(1),
switchMap((token) => {
return next.handle(this.addToken(request, token)); <-- nested return
}));
}
解决方案
TLDR;
return from(this.auth.refreshAccessToken()).pipe(switchMap((token: Token) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(token.access_token);
return next.handle(this.addToken(request, token.access_token));
}));
说明
一件可能会让事情变得更容易的小事,我建议你使用 return 类型的 handle401Error()
而不是 any
作为 return 类型26=] 即 Observable<HttpEvent<any>>
.
你需要做的是 return 从 this.auth.refreshAccessToken().then()
.
next.handle()
的值
可能有多种方法可以做到这一点,但我要推荐 Angular/RxJS 风格。
正如我之前所说,promises 就像 observables,RxJS (v6+) 提供了一种将 promise 转换为 observable 的方法,例如:
import { from } from 'rxjs';
const observable = from(promise);
您可以使用它来将 this.auth.refreshAccessToken()
转换为可观察对象:
from(this.auth.refreshAccessToken())
现在我们有了一个 observable,您可能倾向于使用 subscribe
获取值,但这不是您想要做的,因为您的拦截器正在 returning 一个最终的 observable在其他地方订阅。
您可以改为使用 pipe, which allows you to use a number of operators provided by RxJS. In this case, you want to wait for your first observable refreshAccessToken()
to emit and then you want to return next.handle()
. The commonly used operator for this task is switchMap。
您会注意到您的 else 块实际上正在使用这个:
return this.refreshTokenSubject.pipe(
filter((token) => token !== null),
take(1),
switchMap((token) => { <-- switchMap
return next.handle(this.addToken(request, token));
}));
}
switchMap()
等待第一个 observable 发出,然后将值输出到你的回调函数,期待你 return 另一个 observable。在您的情况下,这意味着您将 then()
替换为 pipe(switchMap())
.
如 TLDR 所示:
return from(this.auth.refreshAccessToken()).pipe(switchMap((token: Token) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(token.access_token);
return next.handle(this.addToken(request, token.access_token));
}));
这应该可以解决您的问题,如果这不起作用,请在下方评论。
句柄 handle401Error 函数不应该 return next.handle(...)。无论如何,您已经涵盖了在拦截函数中被 returned 的内容。而是像正常和 return of(null) 一样将令牌添加到请求中;在 handle401Error 函数中。除非您想抛出自定义错误,否则 catchError 函数应始终 return null。