Gestione di 401s globalmente con Angular

Nel mio progetto Angular 2 faccio chiamate API da servizi che restituiscono un Observable. Il codice chiamante quindi si abbona a questo osservabile. Per esempio:

getCampaigns(): Observable { return this.http.get('/campaigns').map(res => res.json()); } 

Supponiamo che il server restituisca un 401. Come posso rilevare questo errore a livello globale e redirect a una pagina / componente di accesso?

Grazie.


Ecco cosa ho finora:

// boot.ts

 import {Http, XHRBackend, RequestOptions} from 'angular2/http'; import {CustomHttp} from './customhttp'; bootstrap(AppComponent, [HTTP_PROVIDERS, ROUTER_PROVIDERS, new Provider(Http, { useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => new CustomHttp(backend, defaultOptions), deps: [XHRBackend, RequestOptions] }) ]); 

// customhttp.ts

 import {Http, ConnectionBackend, Request, RequestOptions, RequestOptionsArgs, Response} from 'angular2/http'; import {Observable} from 'rxjs/Observable'; @Injectable() export class CustomHttp extends Http { constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) { super(backend, defaultOptions); } request(url: string | Request, options?: RequestOptionsArgs): Observable { console.log('request...'); return super.request(url, options); } get(url: string, options?: RequestOptionsArgs): Observable { console.log('get...'); return super.get(url, options); } } 

Il messaggio di errore che sto ottenendo è “backend.createConnection non è una funzione”

Descrizione

La soluzione migliore che ho trovato è quella di sovrascrivere XHRBackend tale che lo stato di risposta HTTP 401 e 403 conduca a una particolare azione.

Se gestisci l’autenticazione al di fuori dell’applicazione Angular, potresti forzare un aggiornamento della pagina corrente in modo che il meccanismo esterno venga triggersto. I dettagli questa soluzione nell’implementazione di seguito.

È anche ansible inoltrare a un componente all’interno dell’applicazione in modo tale che l’applicazione Angular non venga ricaricata.

Implementazione

Angolare> 2.3.0

Grazie a @mrgoos, ecco una soluzione semplificata per angular 2.3.0+ a causa di una correzione di bug in 2.3.0 angular (consultare il problema https://github.com/angular/angular/issues/11606 ) che estende direttamente il modulo Http .

 import { Injectable } from '@angular/core'; import { Request, XHRBackend, RequestOptions, Response, Http, RequestOptionsArgs, Headers } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/throw'; @Injectable() export class AuthenticatedHttpService extends Http { constructor(backend: XHRBackend, defaultOptions: RequestOptions) { super(backend, defaultOptions); } request(url: string | Request, options?: RequestOptionsArgs): Observable { return super.request(url, options).catch((error: Response) => { if ((error.status === 401 || error.status === 403) && (window.location.href.match(/\?/g) || []).length < 2) { console.log('The authentication session expires or the user is not authorised. Force refresh of the current page.'); window.location.href = window.location.href + '?' + new Date().getMilliseconds(); } return Observable.throw(error); }); } } 

Il file del modulo ora contiene solo il seguente provider.

 providers: [ { provide: Http, useClass: AuthenticatedHttpService } ] 

Un'altra soluzione che utilizza il router e un servizio di autenticazione esterno è dettagliata nel seguente elenco di @mrgoos.

Angolare pre-2.3.0

La seguente implementazione funziona per Angular 2.2.x FINAL e RxJS 5.0.0-beta.12 .

Reindirizza alla pagina corrente (più un parametro per ottenere un URL univoco ed evita la memorizzazione nella cache) se viene restituito un codice HTTP 401 o 403.

 import { Request, XHRBackend, BrowserXhr, ResponseOptions, XSRFStrategy, Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/throw'; export class AuthenticationConnectionBackend extends XHRBackend { constructor(_browserXhr: BrowserXhr, _baseResponseOptions: ResponseOptions, _xsrfStrategy: XSRFStrategy) { super(_browserXhr, _baseResponseOptions, _xsrfStrategy); } createConnection(request: Request) { let xhrConnection = super.createConnection(request); xhrConnection.response = xhrConnection.response.catch((error: Response) => { if ((error.status === 401 || error.status === 403) && (window.location.href.match(/\?/g) || []).length < 2) { console.log('The authentication session expires or the user is not authorised. Force refresh of the current page.'); window.location.href = window.location.href + '?' + new Date().getMilliseconds(); } return Observable.throw(error); }); return xhrConnection; } } 

con il seguente file di modulo.

 import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HttpModule, XHRBackend } from '@angular/http'; import { AppComponent } from './app.component'; import { AuthenticationConnectionBackend } from './authenticated-connection.backend'; @NgModule({ bootstrap: [AppComponent], declarations: [ AppComponent, ], entryComponents: [AppComponent], imports: [ BrowserModule, CommonModule, HttpModule, ], providers: [ { provide: XHRBackend, useClass: AuthenticationConnectionBackend }, ], }) export class AppModule { } 

Angolare 4.3+

Con l’introduzione di HttpClient è arrivata la capacità di intercettare facilmente tutte le richieste / risposte. L’uso generale di HttpInterceptors è ben documentato , vedere l’utilizzo di base e come fornire l’intercettore. Di seguito è riportato un esempio di HttpInterceptor in grado di gestire errori 401.

 import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http' import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/do'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler): Observable> { return next.handle(req).do(event => {}, err => { if (err instanceof HttpErrorResponse && err.status == 401) { // handle 401 errors } }); } } 

L’ Observable ottenuto da ciascun metodo di richiesta è di tipo Observable . L’object Response ha una proprietà status che manterrà il 401 SE il server ha restituito quel codice. Quindi potresti voler recuperarlo prima di mapparlo o convertirlo.

Se si desidera evitare di eseguire questa funzionalità per ogni chiamata, potrebbe essere necessario estendere la class Http Angular 2 e iniettare la propria implementazione che chiama il genitore ( super ) per la normale funzionalità Http e quindi gestire l’errore 401 prima di restituire l’object.

Vedere:

https://angular.io/docs/ts/latest/api/http/index/Response-class.html

Da Angular> = 2.3.0 puoi sovrascrivere il modulo HTTP e iniettare i tuoi servizi. Prima della versione 2.3.0, non è ansible utilizzare i servizi iniettati a causa di un bug di base.

Ho creato un esempio per mostrare come è fatto.

Angolare 4.3+

Per completare la risposta di Gilbert Arenas Dagger :

Se ciò di cui hai bisogno è intercettare qualsiasi errore, applicare un trattamento ad esso e inoltrarlo lungo la catena (e non solo aggiungere un effetto collaterale con .do ), puoi usare HttpClient ei suoi intercettori per fare qualcosa del genere:

 import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/catch'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler): Observable> { // install an error handler return next.handle(req).catch((err: HttpErrorResponse) => { console.log(err); if (err.error instanceof Error) { // A client-side or network error occurred. Handle it accordingly. console.log('An error occurred:', err.error.message); } else { // The backend returned an unsuccessful response code. // The response body may contain clues as to what went wrong, console.log(`Backend returned code ${err.status}, body was: ${err.error}`); } return Observable.throw(new Error('Your custom error')); }); } } 

Per evitare il problema di referenziamento ciclico causato dall’avere servizi come “Router” che vengono iniettati in una class derivata Http, è necessario utilizzare il metodo Injector post-costruttore. Il seguente codice è un’implementazione funzionante di un servizio HTTP che reindirizza alla route di accesso ogni volta che un’API REST restituisce “Token_Expired”. Si noti che può essere utilizzato come sostituzione al normale Http e, in quanto tale, non richiede di modificare nulla nei componenti o servizi già esistenti dell’applicazione.

app.module.ts

  providers: [ {provide: Http, useClass: ExtendedHttpService }, AuthService, PartService, AuthGuard ], 

Angolare> 4.3: ErrorHandler per il servizio di base

 protected handleError(err: HttpErrorResponse | any) { console.log('Error global service'); console.log(err); let errorMessage: string = ''; if (err.hasOwnProperty('status')) { // if error has status if (environment.httpErrors.hasOwnProperty(err.status)) { // predefined errors errorMessage = environment.httpErrors[err.status].msg; } else { errorMessage = `Error status: ${err.status}`; if (err.hasOwnProperty('message')) { errorMessage += err.message; } } } if (errorMessage === '') { if (err.hasOwnProperty('error') && err.error.hasOwnProperty('message')) { // if error has status errorMessage = `Error: ${err.error.message}`; } } // no errors, then is connection error if (errorMessage === '') errorMessage = environment.httpErrors[0].msg; // this.snackBar.open(errorMessage, 'Close', { duration: 5000 }}); console.error(errorMessage); return Observable.throw(errorMessage); }