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。