检索新JWT令牌的角度HttpInterceptor请求导致无限循环

时间:2019-10-03 08:04:33

标签: angular rxjs

在我的应用程序中,我正在使用Angular HttpInterceptor跟踪每个传出的HTTP请求,并将本地存储中存储的JWT令牌添加到请求标头中,以便后端API可以通过检查JWT令牌来授权请求​​。

为此,拦截器只需获取HTTP请求并通过设置标头添加令牌,然后调用next.handle(req)来传播请求。

这是使用以下代码完成的:

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    req = this.addHeaders(req);
    return next.handle(req).pipe(
      catchError(error => {
        if (error instanceof HttpErrorResponse && error.status === 401) {
          this._authService.signOut("unauthorized");
        }
        if (error instanceof HttpErrorResponse && error.status === 403) {
          this._authService.signOut("expired");
        }
        return throwError(error);
      })
    );
  }

  /**
   * Add access_token to header.
   * @param req
   */
  addHeaders(req: HttpRequest<any>): HttpRequest<any> {
    return req.clone({
      setHeaders: {
        "Content-type": "application/json",
        token: this._authService.getAuthenticationToken(),
        jwt_token: this._authService.getAuthorizationToken(),
        [this._config.subscriptionKeyName]: this._config.subscriptionKey || ""
      }
    });
  }

我想扩展此功能,以便拦截器首先检查以确保存储在浏览器本地存储中的JWT令牌有效。如果令牌无效,则拦截器本身应触发单独的HTTP请求,以从API端点检索新的JWT令牌,该令牌应用于覆盖旧令牌,然后再继续原始请求。

我试图弄清楚如何在Angular代码中构造此行为,但我一直遇到无限循环。

这是我目前的代码。

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {

    if (req.headers.has("newtokenrequest")) {
      next.handle(req);
    }

    let jwtToken: string = this._authService.getLocalAuthorizationToken();

    if (!this.checkJwtTokenIsValid(jwtToken)) { //Invalid token, get new one
      const authTokenStream = this._authService.getApiAuthorizationToken$();
      authTokenStream.subscribe(token => {
        this._authService.setAuthorizationToken(token);
      });

    }

    req = this.addHeaders(req);
    return next.handle(req).pipe(
      catchError(error => {
        if (error instanceof HttpErrorResponse && error.status === 401) {
          this._authService.signOut("unauthorized");
        }
        if (error instanceof HttpErrorResponse && error.status === 403) {
          this._authService.signOut("expired");
        }
        return throwError(error);
      })
    );
  }

这是返回Observable<string>的代码,该代码检索新的JWT令牌。

getApiAuthorizationToken$(): Observable<string> {
    let headers = new HttpHeaders();
    headers = headers.set('newtokenrequest', 'true');
    return this._httpClient.get<string>(this._configService.getConfig().teacherAuthAPI, {headers: headers});
  }

代码下降到authTokenStream订阅,然后再次循环回到顶部。我认为这是因为订阅正在向后端发送新的HTTP请求以检索令牌,但是此请求应包括newtokenrequest作为标头,应由应调用{{ 1}},允许请求继续进行而无需进一步的交互,但这不会发生,而是代码循环。

编辑:更新

next.handle(req)

2 个答案:

答案 0 :(得分:0)

您应该做的是将检查本地存储有效性的工作分配给authguard。

canActivate(): Observable<boolean> | Promise<boolean> | boolean {

        switch (this.ispectUrl(this.url)) {
    // I'm checking if there is a jwt in url
          case true: {
            const token = this.refactorUrl(this.url);
            this.auth.setAccessToken(token);
            return this.auth.validateJwt();
            break;
          }
    // No jwt in url? I check possibile jwt in localstorage
          case false: {
            return this.auth.validateJwt();
            break;
          }
    // Neither of both, simply i logout to login page session expired
          default: {
            this.auth.logout();
            return false;
          }

        }
    }

这里我要从url中提取jwt,因为我使用的是CAS实现,但是如果url上没有任何jwt,我只需在执行操作之前检查本地存储,如果jwt有效,则用户可以浏览所有受authguard保护的网址,请记住,在检查jwt有效性之后,您应该提取jwt的到期日期,并从中知道何时检查刷新。

答案 1 :(得分:0)

通过使用HttpBackend服务而不是HttpClient,可以避免在请求授权令牌时出现循环。 HttpBackendHttpClient使用的低级Http服务。它不考虑拦截器。

import {HttpBackend, HttpHeaders, HttpRequest, HttpResponse} from '@angular/common/http';

constructor(private httpBackend: HttpBackend) {
}

getApiAuthorizationToken$(): Observable<string> {
  const headers = new HttpHeaders({
    newtokenrequest: 'true'
  });

  const request = new HttpRequest<string>(
    'GET',
    this._configService.getConfig().teacherAuthAPI,
    {
      headers
    });

  return this.httpBackend.handle(request).pipe(
    map((response: HttpResponse<string>) => response.body),
  );
}

UPD。

替换

const authTokenStream = this._authService.getApiAuthorizationToken$();
authTokenStream.subscribe(token => {
  this._authService.setAuthorizationToken(token);
  return next.handle(req);
});

使用

return this.this._authService.getApiAuthorizationToken$().pipe(
  tap(token => this._authService.setAuthorizationToken(token)),
  switchMap(() => {
    req = this.addHeaders(req);
    return next.handle(req);
  })
);

尝试避免在拦截器内部调用订阅。