Angular 4 Interceptor esegue nuovamente le richieste dopo l’aggiornamento del token

Ciao Sto cercando di capire come implementare i nuovi intercettori angolari e gestire 401 unauthorized errori 401 unauthorized aggiornando il token e riprovando la richiesta. Questa è la guida che ho seguito: https://ryanchenkie.com/angular-authentication-using-the-http-client-and-http-interceptors

Sto memorizzando correttamente nella cache le richieste non riuscite e posso aggiornare il token ma non riesco a capire come inviare nuovamente le richieste che in precedenza non erano riuscite. Voglio anche farlo funzionare con i resolver che sto usando al momento.

token.interceptor.ts

 return next.handle( request ).do(( event: HttpEvent ) => { if ( event instanceof HttpResponse ) { // do stuff with response if you want } }, ( err: any ) => { if ( err instanceof HttpErrorResponse ) { if ( err.status === 401 ) { console.log( err ); this.auth.collectFailedRequest( request ); this.auth.refreshToken().subscribe( resp => { if ( !resp ) { console.log( "Invalid" ); } else { this.auth.retryFailedRequests(); } } ); } } } ); 

authentication.service.ts

 cachedRequests: Array<HttpRequest> = []; public collectFailedRequest ( request ): void { this.cachedRequests.push( request ); } public retryFailedRequests (): void { // retry the requests. this method can // be called after the token is refreshed this.cachedRequests.forEach( request => { request = request.clone( { setHeaders: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${ this.getToken() }` } } ); //??What to do here } ); } 

Il file retryFailedRequests () sopra è ciò che non riesco a capire. Come posso inviare nuovamente le richieste e renderle disponibili per il percorso attraverso il resolver dopo il nuovo tentativo?

Questo è tutto il codice rilevante se questo aiuta: https://gist.github.com/joshharms/00d8159900897dc5bed45757e30405f9

La mia soluzione finale Funziona con richieste parallele.

 export class AuthInterceptor implements HttpInterceptor { authService; refreshTokenInProgress = false; tokenRefreshedSource = new Subject(); tokenRefreshed$ = this.tokenRefreshedSource.asObservable(); constructor(private injector: Injector, private router: Router, private snackBar: MdSnackBar) {} addAuthHeader(request) { const authHeader = this.authService.getAuthorizationHeader(); if (authHeader) { return request.clone({ setHeaders: { "Authorization": authHeader } }); } return request; } refreshToken() { if (this.refreshTokenInProgress) { return new Observable(observer => { this.tokenRefreshed$.subscribe(() => { observer.next(); observer.complete(); }); }); } else { this.refreshTokenInProgress = true; return this.authService.refreshToken() .do(() => { this.refreshTokenInProgress = false; this.tokenRefreshedSource.next(); }); } } logout() { this.authService.logout(); this.router.navigate(["login"]); } intercept(request: HttpRequest, next: HttpHandler): Observable { this.authService = this.injector.get(AuthService); // Handle request request = this.addAuthHeader(request); // Handle response return next.handle(request).catch(error => { if (error.status === 401) { return this.refreshToken() .switchMap(() => { request = this.addAuthHeader(request); return next.handle(request); }) .catch(() => { this.logout(); return Observable.empty(); }); } return Observable.throw(error); }); } } 

Mi sono imbattuto in un problema simile e penso che la logica di raccolta / ripetizione sia eccessivamente complicata. Invece, possiamo semplicemente usare l’operatore catch per controllare il 401, quindi controllare l’aggiornamento del token e rieseguire la richiesta:

 return next.handle(this.applyCredentials(req)) .catch((error, caught) => { if (!this.isAuthError(error)) { throw error; } return this.auth.refreshToken().first().flatMap((resp) => { if (!resp) { throw error; } return next.handle(this.applyCredentials(req)); }); }) as any; 

 private isAuthError(error: any): boolean { return error instanceof HttpErrorResponse && error.status === 401; } 

La soluzione finale di Andrei Ostrovski funziona molto bene, ma non funziona se il token di aggiornamento è scaduto (supponendo che tu stia effettuando una chiamata API per l’aggiornamento). Dopo alcuni scavi, mi sono reso conto che la chiamata all’API del token di aggiornamento veniva intercettata anche dall’intercettore. Ho dovuto aggiungere una dichiarazione if per gestirlo.

  intercept( request: HttpRequest, next: HttpHandler ):Observable { this.authService = this.injector.get( AuthenticationService ); request = this.addAuthHeader(request); return next.handle( request ).catch( error => { if ( error.status === 401 ) { // The refreshToken api failure is also caught so we need to handle it here if (error.url === environment.api_url + '/refresh') { this.refreshTokenHasFailed = true; this.authService.logout(); return Observable.throw( error ); } return this.refreshAccessToken() .switchMap( () => { request = this.addAuthHeader( request ); return next.handle( request ); }) .catch((err) => { this.refreshTokenHasFailed = true; this.authService.logout(); return Observable.throw( err ); }); } return Observable.throw( error ); }); } 

Sulla base di questo esempio , ecco il mio pezzo

 @Injectable({ providedIn: 'root' }) export class AuthInterceptor implements HttpInterceptor { constructor(private loginService: LoginService) { } /** * Intercept request to authorize request with oauth service. * @param req original request * @param next next */ intercept(req: HttpRequest, next: HttpHandler): Observable { const self = this; if (self.checkUrl(req)) { // Authorization handler observable const authHandle = defer(() => { // Add authorization to request const authorizedReq = req.clone({ headers: req.headers.set('Authorization', self.loginService.getAccessToken() }); // Execute return next.handle(authorizedReq); }); return authHandle.pipe( catchError((requestError, retryRequest) => { if (requestError instanceof HttpErrorResponse && requestError.status === 401) { if (self.loginService.isRememberMe()) { // Authrozation failed, retry if user have `refresh_token` (remember me). return from(self.loginService.refreshToken()).pipe( catchError((refreshTokenError) => { // Refresh token failed, logout self.loginService.invalidateSession(); // Emit UserSessionExpiredError return throwError(new UserSessionExpiredError('refresh_token failed')); }), mergeMap(() => retryRequest) ); } else { // Access token failed, logout self.loginService.invalidateSession(); // Emit UserSessionExpiredError return throwError(new UserSessionExpiredError('refresh_token failed')); } } else { // Re-throw response error return throwError(requestError); } }) ); } else { return next.handle(req); } } /** * Check if request is required authentication. * @param req request */ private checkUrl(req: HttpRequest) { // Your logic to check if the request need authorization. return true; } } 

Si consiglia di verificare se l’utente ha abilitato Remember Me per utilizzare il token di aggiornamento per riprovare o semplicemente redirect alla pagina di disconnessione.

Fyi, LoginService ha i seguenti metodi:
– getAccessToken (): string – restituisce l’attuale access_token
– isRememberMe (): boolean – controlla se l’utente ha refresh_token
– refreshToken (): Observable / Promise – Richiesta di oauth server per nuovo access_token utilizzando refresh_token
– invalidateSession (): void – rimuove tutte le informazioni utente e reindirizza alla pagina di disconnessione

Idealmente, si desidera verificare isTokenExpired prima della richiesta inviata. E se scaduto, aggiorna il token e aggiungi aggiornato nell’intestazione.

Oltre a questo retry operator può essere utile la logica del token di aggiornamento sulla risposta 401.

Utilizzare l’ RxJS retry operator nel servizio in cui si sta effettuando una richiesta. Accetta un argomento retryCount . Se non fornito, riproverà la sequenza indefinitamente.

Nell’intercettore su risposta, aggiornare il token e restituire l’errore. Quando il servizio recupera l’errore, ma ora viene utilizzato un operatore di nuovo tentativo, riproverà la richiesta e questa volta con il token aggiornato (Interceptor utilizza token aggiornato da aggiungere nell’intestazione).

 import {HttpClient} from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Rx'; @Injectable() export class YourService { constructor(private http: HttpClient) {} search(params: any) { let tryCount = 0; return this.http.post('https://abcdYourApiUrl.com/search', params) .retry(2); } } 

Nel tuo authentication.service.ts, dovresti avere un HttpClient iniettato come dipendenza

 constructor(private http: HttpClient) { } 

È quindi ansible inviare nuovamente la richiesta (all’interno di retryFailedRequests) come segue:

 this.http.request(request).subscribe((response) => { // You need to subscribe to observer in order to "retry" your request }); 

Ho ottenuto questo creando una nuova richiesta basata sull’URL della richiesta non riuscita e inviando lo stesso corpo della richiesta non riuscita.

  retryFailedRequests() { this.auth.cachedRequests.forEach(request => { // get failed request body var payload = (request as any).payload; if (request.method == "POST") { this.service.post(request.url, payload).subscribe( then => { // request ok }, error => { // error }); } else if (request.method == "PUT") { this.service.put(request.url, payload).subscribe( then => { // request ok }, error => { // error }); } else if (request.method == "DELETE") this.service.delete(request.url, payload).subscribe( then => { // request ok }, error => { // error }); }); this.auth.clearFailedRequests(); 

}