Угловой запрос HttpInterceptor для получения нового токена JWT вызывает бесконечный цикл - PullRequest
1 голос
/ 03 октября 2019

В своем приложении я использую 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-запрос для получения нового токена JWT из конечной точки API, который следует использовать для перезаписи старого, прежде чем продолжить исходный запрос.

Я пытаюсь понять, как построить такое поведение в моем коде 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 в качестве заголовка, который должен быть обнаружен методом перехвата, который должен вызвать next.handle(req),разрешение запроса продолжаться без дальнейшего взаимодействия, однако этого не происходит, и вместо этого код циклично зацикливается.

Редактировать: Обновить

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

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

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

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

    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);
      })
    );
  }

  getApiAuthorizationToken$(): Observable<string> {
    let headers = new HttpHeaders({
      newtokenrequest: 'true'
    });
    // return this._httpClient.get<string>(this._configService.getConfig().teacherAuthAPI, {headers: headers});
    const req = new HttpRequest<string>('GET', this._configService.getConfig().teacherAuthAPI, {headers});
    let res = this._httpBackend.handle(req).pipe(map((response: HttpResponse<string>) => response.body),);
    return res;
  }

Ответы [ 2 ]

0 голосов
/ 03 октября 2019

Вы можете избежать зацикливания при запросе токена авторизации, используя сервис HttpBackend вместо HttpClient. HttpBackend - это Http-сервис низкого уровня, используемый HttpClient. Он не учитывает перехватчики.

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);
  })
);

Старайтесь избегать вызова подписки внутриперехватчик.

0 голосов
/ 03 октября 2019

Что вам нужно сделать, это поручить проверку подлинности localalstorage 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;
          }

        }
    }

Здесь я извлекаю jwt из URL, потому что я работаю с реализацией CAS,но если в URL нет jwt, я просто проверяю localalstorage перед выполнением anithing, если jwt действителен, пользователь может перемещаться по всем URL-адресам, защищенным от authguard, помните, что после проверки действительности jwt вы должны извлечь дату истечения срока действия jwt,и из этого вы должны знать, когда проверять наличие обновлений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...