Я кодирую приложение angular 5. refreshAccessToken
в authentication service
refreshAccessToken(): Observable<ICredentials> {
const refreshTokenUrl = this.urlsService.getUrl(Urls.TOKEN);
const httpParams = new HttpParams()
.append('grant_type', 'refresh_token')
.append('refresh_token', this.credentials.refresh_token)
.append('client_id', Constants.CLIENT_ID)
.toString();
const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
return this.http.post(refreshTokenUrl, httpParams, { headers })
.map((response: any) => {
this.setCredentials(response);
localStorage.setItem(credentialsKey, JSON.stringify(this.getCredentials()));
return response;
});
}
Я хочу реализовать следующий алгоритм:
- Любой HTTP-запрос завершился неудачно из-за несанкционированного доступа со статусом 401
- Попробуйте получить новый токен доступа с сервера
- Повторить запрос
Во время получения нового токена доступа могут быть созданы новые HTTP-запросы, в этом случае я хочу сохранить их и повторить после получения нового токена доступа. Для этого я написал перехватчик.
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { AuthenticationService } from '@app/core/authentication/authentication.service';
import { Urls, UrlsService } from '@app/shared/urls';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class UnauthorizedRequestInterceptor implements HttpInterceptor {
newAccessToken$: Observable<ICredentials> = null;
constructor(
public authService: AuthenticationService,
private router: Router,
private urlsService: UrlsService) {
}
addAuthHeader(request: HttpRequest<any>) {
if (this.authService.getCredentials()) {
return request.clone({
setHeaders: {
'Authorization': 'Bearer ' + this.authService.getCredentials().access_token
}
});
}
return request;
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
request = this.addAuthHeader(request);
return next.handle(request).catch((error: HttpErrorResponse) => {
let handleRequests$ = null;
if (this.isNeedNewAccessToken(error, request)) {
handleRequests$ = this.handleRequestWithNewAccessToken(request, next);
}
return handleRequests$ ||
(this.isUnathorizedError(error)
? Observable.empty()
: Observable.throw(error));
});
}
logout() {
this.authService.logout();
this.router.navigate(['login']);
}
private isNeedNewAccessToken(error: HttpErrorResponse, request: HttpRequest<any>): boolean {
return this.isUnathorizedError(error)
&& this.authService.isAuthenticated()
&& this.isSignInRequest(request);
}
private getNewAccessToken(): Observable<ICredentials> {
if (!this.newAccessToken$) {
this.newAccessToken$ = this.authService.refreshAccessToken();
}
return this.newAccessToken$;
}
private isUnathorizedError(error: HttpErrorResponse) {
return error.status === 401;
}
private handleRequestWithNewAccessToken(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
return this.getNewAccessToken()
.mergeMap(() => {
request = this.addAuthHeader(request);
return next.handle(request);
})
.catch((err: HttpErrorResponse) => {
if (err.error.error === 'invalid_grant') {
this.logout();
}
return Observable.empty();
});
}
private isNotSignInRequest(request: HttpRequest<any>): boolean {
return request.url !== this.urlsService.getUrl(Urls.TOKEN);
}
}
Поведение этого перехватчика действительно странное. На каждом mergeMap
на handleRequestWithNewAccessToken
угловом начинается новый пост httpRequest. Я ожидал, что наблюдаемое, возвращаемое из refreshAccessToken
(функция из authenticationService
, код вверху), будет разрешено только один раз. Я не понимаю, почему он запускается для каждой карты слияния? Я ожидал следующего:
- У меня есть наблюдаемый - HTTP-запрос на токен
- Я использую mergeMap - после завершения http-запроса будут выполнены все обратные вызовы, добавленные с помощью mergeMap.
Я думал хранить запросы, которые мне нужно обрабатывать, в глобальной переменной и вызывать их в запросе subscribe()
to http, но есть проблема, что каждый запрос должен быть разрешен в начальном потоке внутри перехватчика. Я не могу сделать что-то вроде этого: .subscribe(token => this.httpClient.request(storedRequest)
потому что это создаст новый запрос, поэтому все действия должны происходить внутри наблюдаемой цепочки.
Не могли бы вы помочь мне найти решение?
PS Это решение работает, но я хочу избавиться от ненужных запросов TOKEN, например. если странице нужно сделать 5 запросов и токен истек - перехватчик сделает 5 запросов на токен.
share
сделал свое дело, я добавил его внутрьgetNewAccessToken
:this.newAccessToken$ = this.authService.refreshAccessToken().share(); this.newAccessToken$.finally(() => this.newAccessToken$ = null)
. Также для включенияfinally
я добавил импортimport 'rxjs/add/operator/finally';
24.08.2018