令牌拦截器不会再次发送原始请求(Angular,Node.js)

时间:2019-11-19 15:50:28

标签: javascript angular rxjs

我正在尝试在Angular前端中实现令牌拦截器,但是当前的实现存在一个问题。

我的拦截器看起来像这样:

  intercept(request: HttpRequest<any>,next: HttpHandler): Observable<HttpEvent<any>> {
    if (request.headers.get("No-Auth") === "True") {
      return next.handle(request);
    }

    return next.handle(request).pipe(
      catchError(error => {
        if (error instanceof HttpErrorResponse && error.status === 401) {
          this.handle404Error(request, next);
        } else {
          return throwError(error);
        }
      })
    );
  }

和handle404Error函数:

  handle404Error(request: HttpRequest<any>, next: HttpHandler) {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);

      return this.authService.refreshToken().subscribe(
        (res: any) => {
          if (Boolean(res) === true) {
            this.isRefreshing = false;
            this.refreshTokenSubject.next(res);
            return next.handle(request);
          } else {
            return this.authService.logout();
          }
        },
        err => {
          return this.authService.logout();
        },
        () => {
          this.isRefreshing = false;
        }
      );
    } else {
      return this.refreshTokenSubject.pipe(
        filter(res => res != null),
        take(1),
        switchMap(() => {
          return next.handle(request);
        })
      );
    }
  }

当我进行API调用和服务器返回404时,我的拦截器将调用/token端点以获取新的JWT。

请注意,我将JWT存储在服务器上(内存中)的会话对象中,并且在客户端上,我只有sessionID和刷新令牌作为HttpOnly cookie。

问题是,当我进行API调用且JWT无效时,它将设置新的JWT,但不会再次发送先前的请求。 (在JWT过期后,我必须按两次按钮才能获取数据。)

您有什么想法,如何实现此逻辑?

感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

第一件事是不对的-您在拦截器内subscribe,因此您会嵌套订阅,因为Angular在内部订阅了您的请求。

您真正想要的是从return next.handle(request);返回您的catchError。示例:

return next.handle(request).pipe(
  catchError(error => {
    if (error instanceof HttpErrorResponse && error.status === 401) {
      return this.handle404Error(request, next);
    } else {
      return throwError(error);
    }
  })
);

handle404Error(request: HttpRequest<any>, next: HttpHandler) {
  if (!this.isRefreshing) {
    this.isRefreshing = true;
    this.refreshTokenSubject.next(null);

    return this.authService.refreshToken().pipe(
      mergeMap((res: any) => {
        if (Boolean(res) === true) {
          this.isRefreshing = false;
          this.refreshTokenSubject.next(res);
          return next.handle(request);
        } else {
          /* return */ this.authService.logout();
          return of(undefined) // added this because I dont know what this.authService.logout() returns. It must be an Observable
        }
      }),
      catchError((err) => {
        /* return */ this.authService.logout();
        return throwError(err);
      }))      
  } else {
    return this.refreshTokenSubject.pipe(
      filter(res => res != null),
      take(1),
      switchMap(() => {
        return next.handle(request);
      })
    );
  }
}

请注意,我没有检查任何错别字。我也丢掉了这个方块:

() => {
  this.isRefreshing = false;
}

如果要在this.authService.refreshToken()完成时执行它,可以在finalizemergeMap旁边添加catchError运算符。

解释这里实际发生的事情很简单-您可以从intercept方法返回一个可观察到的结果。只需将所有API调用通过管道传递,返回的流将执行它们。