Токен носителя Angular 7 с автоматическим обновлением

У меня есть AuthService, по сути, с двумя методами:

  • getAuthToken (возвращает Promise, поэтому его можно лениво вызывать/вызывать несколько раз с блокировкой ожидания в одном наборе)

  • refreshToken (также возвращает обещание, использует токен обновления, доступный на исходном JWT, для запроса нового токена аутентификации)

Я хотел бы автоматически

  • применить токен носителя к каждому http-запросу (работает)
  • обновить токен при обновлении - и я почти у цели, за исключением того, что результат запроса с обновленным токеном не возвращается к исходному подписчику.

Вот код:

import { HttpEvent, HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from "@angular/common/http";
import { from, Observable } from "rxjs";
import { Injectable } from "@angular/core";
import { AuthService } from "./auth.service";

@Injectable()
export class AuthHttpInterceptor implements HttpInterceptor {
  constructor(
    private _authService: AuthService,
  ) {
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return from(this.addBearerToken(req, next));
  }

  private async addBearerToken(req: HttpRequest<any>, next: HttpHandler): Promise<HttpEvent<any>> {
    const token = await this._authService.getAuthToken();

    const headerSettings = req.headers.keys().reduce(
      (acc, cur) => {
        acc[cur] = req.headers.getAll(cur);
        return acc;
      }, {});

    if (token) {
      headerSettings["Authorization"] = `Bearer ${ token }`;
    } else {
      console.info("performing request without auth!");
    }
    // prevent 302 redirect to challenge on a 401
    headerSettings["X-Requested-With"] = "XMLHttpRequest";
    const
      headers = new HttpHeaders(headerSettings),
      newRequest = req.clone({ headers });
    const result = next.handle(newRequest).toPromise();
    result.catch(async (err) => {
      if (err.status === 401) {
        const
          newToken = await this._authService.refreshToken();
        headerSettings["Authorization"] = `Bearer ${ newToken }`;
        const
          updatedHeaders = new HttpHeaders(headerSettings),
          updatedRequest = req.clone({ headers: updatedHeaders });
        console.info("requery with new token"); // <-- I see this when I have a 401, eg by altering the auth token to be bad, whilst leaving the refresh token alone
        return next.handle(updatedRequest).toPromise().then(data => {
          console.info("requeried data:", data); // <-- I also see this fire, with the valid data coming back from the second request
          return data; // <-- however the original caller doesn't get this data
        });
      }
    });
    return result;

  }
}

Я должен предположить, что это, вероятно, связано с тем, что я смешиваю Observables и Promises (что я делаю, потому что AuthService является асинхронным, используя Promises). Кроме того, если нет 401, первоначальный вызов проходит правильно — как будто цепочка промисов просто отбрасывается после строки

next.handle(newRequest).toPromise();

Что мне не хватает?

Можно ли перейти на Observables? И что произойдет, если вы вернете обещание в конце: return next.handle(updatedRequest).toPromise()

Boland 07.02.2019 21:25

Возврат из исходного .toPromise() работает — и для запросов без ошибки 401 приведенный выше код тоже работает. Переход на Observables, вероятно, возможен, но это повлияет на другие области, которые ожидают здесь обещания; также служба аутентификации хранит обещание, которое разрешило последний действительный токен, чтобы последующие вызовы могли просто получить последний правильный ответ «как есть», что также обрабатывается, если есть несколько вызывающих абонентов, пытающихся получить токен, и первый вызвал обновить. Обещания делают это очень простым - я не уверен, как перевести это в Observables, т.е.

daf 08.02.2019 07:23
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
4
2
8 614
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы разрываете цепочку здесь:

const result = next.handle(newRequest).toPromise();
result.catch(async (err) => {
  ...
});

return result;

result.catch возвращает новое обещание, и ваш обработчик не будет ждать всех действий, которые вы вызвали внутри catch.

Таким образом, вы можете написать это следующим образом:

const result = next.handle(newRequest).toPromise();
return result.catch(async (err) => {
  ...
});

Также то, что вы, вероятно, захотите сделать, это не вызывайте refreshToken несколько раз, когда он выполняется

cachedRequest: Promise<any>; // define prop in your class
...

if (!this.cachedRequest) {
  this.cachedRequest = this.authService.refreshToken();
}
const  newToken = await this.cachedRequest;
this.cachedRequest = null;

Вот Простая демонстрация, чтобы вы могли его протестировать. (Я обрабатываю 404 там, но это не имеет значения)

Хорошо подмечено - это была разорванная цепочка, которая вызвала проблему)': Кроме того, authService уже имеет дело со сценарием множественных запросов - вот почему он асинхронный (т.е. возвращает обещание) - если запрос аутентификации уже выполняется (или последний уже сработал, вызывающие абоненты просто возвращают незавершенное или действительное обещание, поэтому мне не нужно заботиться о кэшировании запросов или что-то в этом роде (:

daf 11.02.2019 06:59

(Кроме того, я должен признать, что чувствую себя довольно глупо из-за того, что был озадачен этим - я не только потреблял промисы буквально годами, я написал (4, iirc) реализации промисов, дважды ради интереса и дважды (включая переписывание) для npmjs.com/package/synchronous-обещание -- вы бы считать Я бы заметил такую ​​вопиющую ошибку ): Тем не менее, еще раз спасибо, @yurzui!

daf 11.02.2019 13:13

Другие вопросы по теме