Angular 4.3 - HTTP-перехватчик - обновить токен JWT

Мне нужно отреагировать (в классе перехватчика) на 403 Запрещенный статус HTTP (для получения / обновления) токена JWT и повторить запрос со свежим токеном.

В приведенном ниже коде, когда сервер возвращает ответ с ошибкой, он переходит к обратному вызову успеха (а не к обратному вызову ошибки, как я ожидаю), а событие - это объект typeof (который бесполезен в реакции на ответ об ошибке). Объект события выглядит так: {type: 0}.

Вопрос:

-Как правильно обрабатывать httpErrorResponse (403 Forbidden) в HttpInterceptor, когда мне нужно обновить accessToken и повторить HTTP-запрос?

 import {
  HttpInterceptor,
  HttpRequest,
  HttpResponse,
  HttpHandler,
  HttpEvent
} from '@angular/common/http';
import 'rxjs/add/operator/map';

@Injectable()
class JWTInterceptor implements HttpInterceptor {

  constructor(private tokenService: TokenService) {}
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  let myHeaders = req.headers;
  if (this.tokenService.accessToken) {
        myHeaders = myHeaders.append('Authorization',`${this.tokenService.accessToken.token_type} ${this.tokenService.accessToken.access_token}`)
   }

  const authReq = req.clone({headers: myHeaders});

    return next.handle(authReq).map((event: HttpEvent<any>) => {
      if (event instanceof HttpResponse) {
        // success callback
      }
    }, (err: any) => {
      if (err instanceof HttpErrorResponse {
        if (err.status === 403) {
          // error callback
          this.tokenService.obtainAccessToken()
        }
      }
    })
      .retry(1);
  }
}

person kuceraf    schedule 25.07.2017    source источник


Ответы (3)


Мое окончательное решение этой проблемы:

@Injectable()
export class WebApiInterceptor implements HttpInterceptor {
  constructor(private tokenService: TokenService) {
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.log('*An intercepted httpRequest*', req, this.tokenService.accessToken);
    const authReq = this.authenticateRequest(req);
    console.log('*Updated httpRequest*', authReq);
    return next.handle(authReq)
      .map((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          console.log('*An intercepted httpResponse*', event);
          return event;
        }
      })
      .catch((error: any) => {
        if (error instanceof HttpErrorResponse) {
          if (error.status === 403 && error.url !== environment.authEndpoint) {
            return this.tokenService
              .obtainAccessToken()
              .flatMap((token) => {
                const authReqRepeat = this.authenticateRequest(req);
                console.log('*Repeating httpRequest*', authReqRepeat);
                return next.handle(authReqRepeat);
              });
          }
        } else {
          return Observable.throw(error);
        }
      })
  }
}

Функция

authenticateRequest(req)

просто добавляет заголовок авторизации к копии исходного запроса

Функция

obtainAccessToken()

получить свежий токен с сервера авторизации и сохранить его

person kuceraf    schedule 26.07.2017

Вам нужно добавить оператор catch из RxJS. Здесь будет ошибка, и вы можете обработать ее соответствующим образом.

Когда вы получаете сообщение об ошибке статуса 0, это, скорее всего, означает, что удаленный сервер не работает и соединение не может быть установлено.

Взгляните на мой пример логики:

this.http.request(url, options)
        .map((res: Response) => res.json())
        .catch((error: any) => {
            const err = error.json();

            // Refresh JWT
            if (err.status === 403) {
                // Add your token refresh logic here.
            }

            return Observable.throw(err);
        });

Чтобы ваша логика обновления прошла через перехватчик, вам нужно вернуть вызов, и функция также должна вернуть Observable. Например, изменение исходной логики выше:

this.http.request(url, options)
        .map((res: Response) => res.json())
        .catch((error: any) => {
            const err = error.json();

            // Refresh JWT
            if (err.status === 403) {
                // refreshToken makes another HTTP call and returns an Observable.
                return this.refreshToken(...);
            }

            return Observable.throw(err);
        });

Если вы хотите иметь возможность повторить исходный запрос, вы можете передать исходные данные запроса, чтобы при успешном обновлении токена вы могли снова позвонить. Например, так могла бы выглядеть ваша refreshToken функция:

refreshToken(url: stirng, options: RequestOptionsArgs, body: any, tokenData: any): Observable<any>
    return this.post(`${this.url}/token/refresh`, tokenData)
        .flatMap((res: any) => {
            // This is where I retry the original request
            return this.request(url, options, body);
        });
}
person Lansana Camara    schedule 25.07.2017
comment
Это решает часть моей проблемы - теперь я могу получить доступ к HttpErrorResponse и вызвать логику обновления / получения токена - спасибо за это. Но когда я вызываю .retry (1), новый HTTP-запрос снова не проходит через перехватчик (поэтому заголовки JWT не добавляются в новый вызов) - person kuceraf; 25.07.2017
comment
Что вам нужно сделать в вашей функции обновления, так это вернуть Observable. Затем верните вызов этой функции обновления в этом блоке catch, и она добавится к перехватчику. Обновленный ответ например. - person Lansana Camara; 25.07.2017

Просто хотел поделиться тем, что сработало для меня:

@Injectable()
export class AutoReLoginInterceptor implements HttpInterceptor {

    constructor() {
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // as we want to intercept the possible errors, instead of directly returning the request execution, we return an Observable to control EVERYTHING
        return new Observable<HttpEvent<any>>(subscriber => {

            // first try for the request
            next.handle(req)
                .subscribe((event: HttpEvent<any>) => {
                        if (event instanceof HttpResponse) {
                            // the request went well and we have valid response
                            // give response to user and complete the subscription
                            subscriber.next(event);
                            subscriber.complete();
                        }
                    },
                    error => {
                        if (error instanceof HttpErrorResponse && error.status === 401) {
                            console.log('401 error, trying to re-login');

                            // try to re-log the user
                            this.reLogin().subscribe(authToken => {
                                // re-login successful -> create new headers with the new auth token
                                let newRequest = req.clone({
                                    headers: req.headers.set('Authorization', authToken)
                                });

                                // retry the request with the new token
                                next.handle(newRequest)
                                    .subscribe(newEvent => {
                                        if (newEvent instanceof HttpResponse) {
                                            // the second try went well and we have valid response
                                            // give response to user and complete the subscription
                                            subscriber.next(newEvent);
                                            subscriber.complete();
                                        }
                                    }, error => {
                                        // second try went wrong -> throw error to subscriber
                                        subscriber.error(error);
                                    });
                            });
                        } else {
                            // the error was not related to auth token -> throw error to subscriber
                            subscriber.error(error);
                        }
                    });
        });

    }

    /**
     * Try to re-login the user.
     */
    private reLogin(): Observable<string> {
        // obtain new authorization token and return it
    }
}
person Alfergon    schedule 09.02.2018
comment
Идеально. Благодарю. Заголовок авторизации должен включать префикс Bearer. - person ashraf; 23.03.2018