Я создал перехватчик ошибок в своем первом приложении Angular, которое для меня является новым. Перехватчик пытается обновить токен авторизации Firebase, когда появляется код ответа 401
. Поэтому я написал следующий код:
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private authService: AuthService, private alertService: AlertService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError(err => {
if (err.status === 401) {
let user = localStorage.getItem('currentUser');
if (!user) {
this.logout(false);
return throwError(err.error);
}
let currentUser = JSON.parse(user);
if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
this.logout(false);
return throwError(err.error);
}
const reference = this;
this.authService.getToken(currentUser, true).then(t => {
// How do I await and return this properly?
return reference.updateTokenAndRetry(request, next, currentUser, t);
}); // Get token and refresh
}
this.alertService.showAlert({
text: 'Fout tijdens het verzenden van het verzoek',
});
return throwError(err.error);
})
);
}
updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
// Update local stored user
currentUser.stsTokenManager.accessToken = token;
localStorage.setItem('currentUser', JSON.stringify(currentUser));
// Add the new token to the request
request = request.clone({
setHeaders: {
Authorization: token,
},
});
return next.handle(request);
}
Токен обновляется нормально. Однако сетевой вызов не выполняется после обновления, что должно выполняться reference.updateTokenAndRetry(request, next, currentUser, t);
.
Я предполагаю, что причина этого в том, что this.authService.getToken(currentUser, true)
возвращает Promise
(это плагин Firebase, и его нельзя изменить). Я хочу вернуть return reference.updateTokenAndRetry(request, next, currentUser, t);
, но это невозможно, так как он находится в асинхронном функциональном блоке.
Как я могу дождаться или ответить на следующий сетевой вызов? Я не могу сделать функцию intercept
async
. Я довольно застрял на этом этапе.
Вместо того, чтобы пытаться вернуть асинхронное обещание, вы должны преобразовать свое обещание в наблюдаемое с помощью оператора from RxJS, как описано в этом посте: Преобразовать обещание в наблюдаемое.
Это приведет к правильному возвращаемому типу Observable> для вашего перехватчика.
Ваш код будет выглядеть примерно так (при условии, что вы отправляете только один запрос за раз):
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError(err => {
if (err.status === 401) {
let user = localStorage.getItem('currentUser');
if (!user) {
this.logout(false);
return throwError(err.error);
}
let currentUser = JSON.parse(user);
if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
this.logout(false);
return throwError(err.error);
}
// Return a newly created function here
return this.refreshToken(currentUser, request, next);
}
this.alertService.showAlert({
text: 'Fout tijdens het verzenden van het verzoek',
});
return throwError(err.error);
})
);
}
refreshToken(currentUser: any, request: any, next: any) {
// By making use of the from operator of RxJS convert the promise to an observable
return from(this.authService.getToken(currentUser, true)).pipe(
switchMap(t => this.updateTokenAndRetry(request, next, currentUser, t))
)
}
updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
// Update local stored user
currentUser.stsTokenManager.accessToken = token;
localStorage.setItem('currentUser', JSON.stringify(currentUser));
// Add the new token to the request
request = request.clone({
setHeaders: {
Authorization: token,
},
});
return next.handle(request);
}
Надеюсь это поможет!
@Пилатус, интересно. Что я могу сделать против этого? Не стесняйтесь добавлять свой ответ.
Вы можете поместить токен в объект поведения (таким образом вы все еще можете вернуть наблюдаемое и получить последнее значение). Кроме того, вы должны автоматически обновить свой токен (через x минут, но до истечения срока его действия. См. auth0.com/docs/quickstart/spa/angular2/05-токен-обновление для примера кода)
@ Mr.wiseguy, вы правы, единственная проблема заключается в том, что пользователь возвращается после истечения срока действия токена. Запросы, сделанные в intercepter
, все еще должны дождаться первоначального вызова refreshtoken
.
Решение Arwin работает хорошо, но только в среде, где за один раз отправляется один запрос.
Чтобы заставить это работать, сохраните метод refreshToken
в Observable
с трубой share
. Это позволит нескольким подписчикам, но одному результату.
Оберните метод next.handle(request)
другим Subject<any>
и верните тему.
Если запрос запускает вызов ошибки, который не является 401
вызовом ошибки subject.error
.
После обновления токена вызовите this.updateTokenAndRetry(request, next, currentUser, token).subscribe(result => subject.next(result);
, чтобы убедиться, что запрос возвращен первоначальному подписчику.
Код ниже является псевдокодом и должен работать в вашем случае.
refreshTokenObservable: Observable<any>;
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let subject = new Subject<any>();
next.handle(request).pipe(
catchError(err => {
if (err.status === 401) {
let user = localStorage.getItem('currentUser');
if (!user) {
this.logout(false);
subject.error(err.error);
return;
}
let currentUser = JSON.parse(user);
if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
this.logout(false);
subject.error(err.error);
return;
}
// Return a newly created function here
this.refreshToken(currentUser).subscribe(token => {
this.updateTokenAndRetry(request, next, currentUser, token).subscribe(result => subject.next(result);
this.refreshTokenObservable = null; // clear observable for next failed login attempt
});
}
this.alertService.showAlert({
text: 'Fout tijdens het verzenden van het verzoek',
});
subject.error(err.error);
})
).subscribe(result => subject.next(result));
return subject.asObservable();
}
refreshToken(currentUser: any) {
if (this.refreshTokenObservable == null)
{
// By making use of the from operator of RxJS convert the promise to an observable
this.refreshTokenObservable = from(this.authService.getToken(currentUser, true)).pipe(share());
}
return this.refreshTokenObservable;
}
updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
// Update local stored user
currentUser.stsTokenManager.accessToken = token;
localStorage.setItem('currentUser', JSON.stringify(currentUser));
// Add the new token to the request
request = request.clone({
setHeaders: {
Authorization: token,
},
});
return next.handle(request);
}
@GiovanniTerlingen Это будет хорошо работать только с одним запросом за раз. При обработке нескольких вызовов и ошибок аутентификации ваш метод
refreshToken
также будет вызываться несколько раз.