page change angular

change - Manejo de 401 a nivel mundial con Angular



page title angular 4 (8)

Angular 4.3+

Con la introducción de HttpClient surgió la capacidad de interceptar fácilmente todas las solicitudes / respuestas. El uso general de HttpInterceptors está bien documented , consulte el uso básico y cómo proporcionar el interceptor. A continuación se muestra un ejemplo de un HttpInterceptor que puede manejar errores 401.

Actualizado para RxJS 6+

import { Observable, throwError } from ''rxjs''; import { HttpErrorResponse, HttpEvent, HttpHandler,HttpInterceptor, HttpRequest } from ''@angular/common/http''; import { Injectable } from ''@angular/core''; import { catchError } from ''rxjs/operators''; @Injectable() export class ErrorInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req).pipe( catchError((err: HttpErrorResponse) => { if (err.status == 401) { // Handle 401 error } else { return throwError(err); } }) ); } }

RxJS <6

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<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req).do(event => {}, err => { if (err instanceof HttpErrorResponse && err.status == 401) { // handle 401 errors } }); } }

En mi proyecto Angular 2, realizo llamadas API desde servicios que devuelven un Observable. El código de llamada se suscribe a este observable. Por ejemplo:

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

Digamos que el servidor devuelve un 401. ¿Cómo puedo detectar este error globalmente y redirigirlo a una página / componente de inicio de sesión?

Gracias.

Esto es lo que tengo hasta ahora:

// 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<Response> { console.log(''request...''); return super.request(url, options); } get(url: string, options?: RequestOptionsArgs): Observable<Response> { console.log(''get...''); return super.get(url, options); } }

El mensaje de error que recibo es "backend.createConnection no es una función"


Descripción

La mejor solución que he encontrado es anular el XHRBackend modo que el estado de respuesta HTTP 401 y 403 conduzca a una acción particular.

Si maneja su autenticación fuera de su aplicación Angular, podría forzar una actualización de la página actual de modo que se active su mecanismo externo. Detallo esta solución en la implementación a continuación.

También puede reenviar a un componente dentro de su aplicación para que su aplicación Angular no se vuelva a cargar.

Implementación

Angular> 2.3.0

Gracias a @mrgoos, aquí hay una solución simplificada para angular 2.3.0+ debido a una corrección de errores en angular 2.3.0 (vea el problema https://github.com/angular/angular/issues/11606 ) que se extiende directamente al módulo 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<Response> { 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); }); } }

El archivo del módulo ahora solo contiene el siguiente proveedor.

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

Otra solución que utiliza Router y un servicio de autenticación externo se detalla en el siguiente resumen de @mrgoos.

Angular pre-2.3.0

La siguiente implementación funciona para Angular 2.2.x FINAL y RxJS 5.0.0-beta.12 .

Redirige a la página actual (más un parámetro para obtener una URL única y evitar el almacenamiento en caché) si se devuelve un código 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 el siguiente archivo de módulo.

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 { }


Como las API frontend expiran más rápido que la leche, con Angular 6+ y RxJS 5.5+, debe usar pipe :

import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpErrorResponse } from ''@angular/common/http''; import { Observable, throwError } from ''rxjs''; import { Injectable } from ''@angular/core''; import { catchError } from ''rxjs/operators''; import { Router } from ''@angular/router''; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private router: Router) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req).pipe( catchError((err: HttpErrorResponse) => { if (err.status === 401) { this.router.navigate([''login''], { queryParams: { returnUrl: req.url } }); } return throwError(err); }) ); } }

Actualización para Angular 7+ y rxjs 6+

import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from ''@angular/common/http''; import { Observable, of } from ''rxjs''; import { Injectable } from ''@angular/core''; import { catchError } from ''rxjs/internal/operators''; import { Router } from ''@angular/router''; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private router: Router) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(request) .pipe( catchError((err, caught: Observable<HttpEvent<any>>) => { if (err instanceof HttpErrorResponse && err.status == 401) { this.router.navigate([''login''], { queryParams: { returnUrl: req.url } }); return of(err as any); } throw err; }) ); } }


El Observable que obtiene de cada método de solicitud es del tipo Observable<Response> . El objeto de Response tiene una propiedad de status que contendrá el 401 SI el servidor devolvió ese código. Por lo tanto, es posible que desee recuperar eso antes de asignarlo o convertirlo.

Si desea evitar hacer esta funcionalidad en cada llamada, es posible que deba extender la clase Http Angular 2 e inyectar su propia implementación que llame al padre ( super ) para la funcionalidad Http normal y luego manejar el error 401 antes de devolver el objeto.

Ver:

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


Para evitar el problema de referencia cíclica causado por la inyección de servicios como "Router" en una clase derivada de Http, se debe utilizar el método Inyector posterior al constructor. El siguiente código es una implementación funcional de un servicio Http que redirige a la ruta de inicio de sesión cada vez que una API REST devuelve "Token_Expired". Tenga en cuenta que puede usarse como una sustitución del Http normal y, como tal, no requiere cambiar nada en los componentes o servicios ya existentes de su aplicación.

app.module.ts

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

Extended-http.service.ts

import { Injectable, Injector } from ''@angular/core''; import { Request, XHRBackend, RequestOptions, Response, Http, RequestOptionsArgs, Headers } from ''@angular/http''; import { Observable } from ''rxjs/Observable''; import { Router } from ''@angular/router''; import { AuthService } from ''./auth.service''; import ''rxjs/add/operator/catch''; import ''rxjs/add/observable/throw''; @Injectable() export class ExtendedHttpService extends Http { private router; private authService; constructor( backend: XHRBackend, defaultOptions: RequestOptions, private injector: Injector) { super(backend, defaultOptions); } request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> { if (typeof url === ''string'') { if (!options) { options = { headers: new Headers() }; } this.setHeaders(options); } else { this.setHeaders(url); } console.log("url: " + JSON.stringify(url) +", Options:" + options); return super.request(url, options).catch(this.catchErrors()); } private catchErrors() { return (res: Response) => { if (this.router == null) { this.router = this.injector.get(Router); } if (res.status === 401 || res.status === 403) { //handle authorization errors //in this example I am navigating to login. console.log("Error_Token_Expired: redirecting to login."); this.router.navigate([''signin'']); } return Observable.throw(res); }; } private setHeaders(objectToSetHeadersTo: Request | RequestOptionsArgs) { if (this.authService == null) { this.authService = this.injector.get(AuthService); } //add whatever header that you need to every request //in this example I could set the header token by using authService that I''ve created //objectToSetHeadersTo.headers.set(''token'', this.authService.getToken()); } }


Angular 4.3+

Para completar la respuesta The Gilbert Arenas Dagger :

Si lo que necesita es interceptar cualquier error, aplicarle un tratamiento y reenviarlo por la cadena (y no solo agregar un efecto secundario con .do ), puede usar HttpClient y sus interceptores para hacer algo por el estilo:

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<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 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'')); }); } }


Angular> 4.3: ErrorHandler para el servicio 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); }


Desde Angular> = 2.3.0 puede anular el módulo HTTP e inyectar sus servicios. Antes de la versión 2.3.0, no podía usar sus servicios inyectados debido a un error central.

He creado una gist para mostrar cómo se hace.