У меня есть AuthService, по сути, с двумя методами:
getAuthToken (возвращает Promise, поэтому его можно лениво вызывать/вызывать несколько раз с блокировкой ожидания в одном наборе)
refreshToken (также возвращает обещание, использует токен обновления, доступный на исходном JWT, для запроса нового токена аутентификации)
Я хотел бы автоматически
Вот код:
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();
Что мне не хватает?
Возврат из исходного .toPromise() работает — и для запросов без ошибки 401 приведенный выше код тоже работает. Переход на Observables, вероятно, возможен, но это повлияет на другие области, которые ожидают здесь обещания; также служба аутентификации хранит обещание, которое разрешило последний действительный токен, чтобы последующие вызовы могли просто получить последний правильный ответ «как есть», что также обрабатывается, если есть несколько вызывающих абонентов, пытающихся получить токен, и первый вызвал обновить. Обещания делают это очень простым - я не уверен, как перевести это в Observables, т.е.





Вы разрываете цепочку здесь:
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 уже имеет дело со сценарием множественных запросов - вот почему он асинхронный (т.е. возвращает обещание) - если запрос аутентификации уже выполняется (или последний уже сработал, вызывающие абоненты просто возвращают незавершенное или действительное обещание, поэтому мне не нужно заботиться о кэшировании запросов или что-то в этом роде (:
(Кроме того, я должен признать, что чувствую себя довольно глупо из-за того, что был озадачен этим - я не только потреблял промисы буквально годами, я написал (4, iirc) реализации промисов, дважды ради интереса и дважды (включая переписывание) для npmjs.com/package/synchronous-обещание -- вы бы считать Я бы заметил такую вопиющую ошибку ): Тем не менее, еще раз спасибо, @yurzui!
Можно ли перейти на Observables? И что произойдет, если вы вернете обещание в конце: return next.handle(updatedRequest).toPromise()