令牌刷新后,角度 http 拦截器不再调用请求

Mát*_*tal 5 javascript http interceptor typescript angular

我的项目中有一个 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);
  }
}));
Run Code Online (Sandbox Code Playgroud)

和 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));
          }));
    }
  }
Run Code Online (Sandbox Code Playgroud)

我从一篇文章中创建了拦截器,它应该可以正常工作,令牌刷新就像一个魅力,但是

return next.handle(this.addToken(request, token.access_token));
Run Code Online (Sandbox Code Playgroud)

哪个应该使用现在有效的令牌再次调用请求只是不调用它。

RJM*_*RJM 6

问题

this.auth.refreshAccessToken()返回一个承诺(我假设给定了.then())。

解释

以防万一您不熟悉promise,它们是处理异步代码的常用系统。这是文档链接

this.auth.refreshAccessToken().then()接受一个函数作为参数,因为是共同的,你提供了一个匿名箭头功能(token: Token) => { ... }

当你这样做时return next.handle(this.addToken(request, token.access_token));,你是在箭头函数中,所以你实际上不是从 返回一个值handle401Error(),而是返回一个值到.then()

.then() 确实返回一个值,但您目前没有返回该值。

您可以在 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
          }));
    }
Run Code Online (Sandbox Code Playgroud)

解决方案

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));
      }));
Run Code Online (Sandbox Code Playgroud)

解释

这可能使事情变得更容易一件小事,我会建议,而不是any作为返回类型handle401Error()使用返回类型的handle.next()这是Observable<HttpEvent<any>>

您需要做的是next.handle()从 inside返回值this.auth.refreshAccessToken().then()

可能有多种方法可以做到这一点,但我将推荐 Angular/RxJS 风格。

正如我之前所说,promise 就像 observables,而 RxJS (v6+) 提供了一种将 promise 转换为 observable 的方法,例如:

import { from } from 'rxjs';
const observable = from(promise);
Run Code Online (Sandbox Code Playgroud)

您可以使用它来转换this.auth.refreshAccessToken()为 observable:

from(this.auth.refreshAccessToken())
Run Code Online (Sandbox Code Playgroud)

现在我们有了一个 observable,您可能倾向于使用它来获取值,subscribe但这不是您想要做的,因为您的拦截器正在返回一个在其他地方订阅的最终 observable。

你可以做的是使用pipe,它允许你使用 RxJS 提供的许多运算符。在这种情况下,您希望等待第一个 observablerefreshAccessToken()发出,然后返回next.handle()。此任务常用的运算符是switchMap

你会注意到你的 else 块实际上是在使用这个:

return this.refreshTokenSubject.pipe(
          filter((token) => token !== null),
          take(1),
          switchMap((token) => {                                <-- switchMap
            return next.handle(this.addToken(request, token));
          }));
    }
Run Code Online (Sandbox Code Playgroud)

switchMap()等待第一个 observable 发出,然后将值输出到您的回调函数中,期待您返回另一个 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));
      }));
Run Code Online (Sandbox Code Playgroud)

这应该可以解决您的问题,如果这不起作用,请在下面发表评论。