Angular로 401을 전 세계적으로 처리


92

내 Angular 2 프로젝트에서 Observable을 반환하는 서비스에서 API 호출을합니다. 그런 다음 호출 코드는이 옵저버 블을 구독합니다. 예를 들면 :

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

서버가 401을 반환한다고 가정 해 보겠습니다.이 오류를 전역 적으로 포착하고 로그인 페이지 / 구성 요소로 리디렉션하려면 어떻게해야합니까?

감사.


지금까지 내가 가진 내용은 다음과 같습니다.

// 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);
    }
}

내가받은 오류 메시지는 "backend.createConnection이 함수가 아닙니다"입니다.


1
나는 이것이 당신에게 줄 수 있다고 생각 약간 포인터
— 판 카즈 Parkar

답변:


79

기술

내가 찾은 가장 좋은 해결책은 무시하는 것 XHRBackend같은 그 HTTP 응답 상태 401와 403특정 작업에 리드를.

Angular 애플리케이션 외부에서 인증을 처리하는 경우 외부 메커니즘이 트리거되도록 현재 페이지를 강제로 새로 고칠 수 있습니다. 이 솔루션은 아래 구현에서 자세히 설명합니다.

Angular 애플리케이션이 다시로드되지 않도록 애플리케이션 내부의 구성 요소로 전달할 수도 있습니다.

이행

각도> 2.3.0

@mrgoos 덕분에 angular 2.3.0의 버그 수정 (문제 https://github.com/angular/angular/issues/11606 참조 )으로 인해 angular 2.3.0+에 대한 단순화 된 솔루션 이 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);
        });
  }
}

이제 모듈 파일에는 다음 공급자 만 포함됩니다.

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

라우터와 외부 인증 서비스를 사용하는 또 다른 솔루션은 @mrgoos 의 다음 요점 에 자세히 설명되어 있습니다.

Angular pre-2.3.0

다음 구현은 Angular 2.2.x FINAL및 RxJS 5.0.0-beta.12.

HTTP 코드 401 또는 403이 반환되면 현재 페이지 (고유 URL을 가져오고 캐싱을 방지하는 매개 변수 포함)로 리디렉션됩니다.

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

}

다음 모듈 파일로.

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

2
감사! 나는 내 문제를 알아 냈다 ... 나는이 줄을 놓치고 catch()있었기 때문에 찾을 수 없었다. (SMH) import "rxjs/add/operator/catch";
— hartpdx

1
라우터 모듈을 사용하여 탐색을 수행 할 수 있습니까?
— Yuanfei Zhu의

1
Auth Guard와 번들링을위한 훌륭한 솔루션! 1. Auth Guard가 승인 된 사용자를 확인합니다 (예 : LocalStorage를 조사하여). 2. 401/403 응답에서 Guard에 대한 인증 된 사용자를 정리합니다 (예 : LocalStorage에서 코어 응답 매개 변수 제거). 3.이 초기 단계에서는 로그인 페이지로 전달하기 위해 라우터에 액세스 할 수 없습니다. 동일한 페이지를 새로 고치면 Guard 검사가 트리거되어 로그인 화면으로 전달됩니다 (선택적으로 초기 URL을 보존하므로 인증 성공 후 요청 된 페이지로 전달됩니다).
— Alex Klaus

1
안녕하세요 @NicolasHenneaux-대체하는 것이 더 낫다고 생각하는 이유는 http무엇입니까? 내가 보는 유일한 이점은 단순히 공급자로 넣을 수 있다는 것입니다. { provide: XHRBackend, useClass: AuthenticationConnectionBackend }Http를 재정의 할 때 더 어색한 코드를 작성 useFactory하고 'new'를 호출하고 특정 인수를 전송하여 자신을 제한 해야합니다 . 어떻게 생각하십니까? 두 번째 방법에 대한 참조 : adonespitogo.com/articles/angular-2-extending-http-provider
— mrgoos

3
- @Brett은 내가 당신을 도움이 될 것입니다 그것을위한 요점 만든 gist.github.com/mrgoos/45ab013c2c044691b82d250a7df71e4c
— mrgoos

84

Angular 4.3 이상

HttpClient 의 도입으로 모든 요청 / 응답을 쉽게 가로 챌 수있는 기능이 생겼습니다. HttpInterceptors의 일반적인 사용법은 잘 문서화 되어 있으며 기본 사용법과 인터셉터를 제공하는 방법을 참조하십시오. 다음은 401 오류를 처리 할 수있는 HttpInterceptor의 예입니다.

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
            }
        });
    }
}

1
여전히 효과가 있습니까? 어제 그것은 나를 위해 작동했지만 다른 모듈을 설치 한 후에 다음 오류가 발생합니다. next.handle (…) .do는 함수가 아닙니다
— Multitut

HTTP는 거의 항상 냄새처럼 나는이 하나가 수업의 연장으로 사용되어야한다고 생각
— kboom

1
HTTP_INTERCEPTORS를 사용하여 공급자 목록에 추가하는 것을 잊지 마십시오. 문서
— Bruno Peres 2017 년

2
훌륭하지만 Router여기에서 사용 하는 것이 작동하지 않는 것 같습니다. 예를 들어, 사용자가 401-403을 받으면 로그인 페이지로 라우팅하고 싶지만 this.router.navigate(['/login']작동하지 않습니다. 그것은 아무것도하지 않는다
— CodyBugstein

".do is not a function"이 표시되면 import 'rxjs/add/operator/do';rxjs를 가져온 후 추가하십시오 .
— amoss

20

Angular 6+ 및 RxJS 5.5+에서는 프론트 엔드 API가 우유보다 빨리 만료되므로 다음을 사용해야합니다 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);
      })
    );
  }
}

Angular 7+ 및 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: request.url } });
            return of(err as any);
          }
          throw err;
        })
      );
  }
}


내가 할 error TS2322: Type 'Observable<{}>' is not assignable to type 'Observable<HttpEvent<any>>'.(가) 할 때 .pipe거기에 어떤 오류가 때 나는 제거하지.pipe
— BlackICE에

2
@BlackICE 내 대답의 첫 번째 문장을 재확인하는 것 같습니다. 최신 버전에 대한 답변으로 업데이트했습니다.
— Saeb Amini

1
귀하의 ng7 + 예제 req에서 실제로 request는 편집이 작습니다
— ask_io

12

그만큼 Observable각 요청 방법에서 얻는 유형이다 Observable<Response>. Response객체는이 status를 개최한다 속성을401 서버가 해당 코드를 반환 경우입니다. 따라서 매핑하거나 변환하기 전에 검색 할 수 있습니다.

각 호출에서이 기능을 수행하지 않으려면 Angular 2의 Http클래스 를 확장 super하고 일반 Http기능에 대해 부모 ( )를 호출하는 자체 구현을 삽입 한 다음401 객체를 반환하기 전에 오류 할 수 있습니다.

보다:

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


따라서 Http를 확장하면 Http 내에서 "로그인"경로로 리디렉션 할 수 있어야합니까?
— pbz

그것이 이론입니다. 이를 수행하려면 라우터를 Http 구현에 삽입해야합니다.
— Langley

당신의 도움을 주셔서 감사합니다. 샘플 코드로 질문을 업데이트했습니다. 나는 아마도 (Angular를 처음 사용하는) 뭔가 잘못하고 있습니다. 그게 뭔지 아세요? 감사.
— pbz

기본 Http 공급자를 사용하고 있으므로 기본 공급자 대신 클래스의 인스턴스로 확인되는 고유 한 공급자를 만들어야합니다. 참조 : angular.io/docs/ts/latest/api/core/Provider-class.html
— Langley

1
@Langley, 감사합니다. 당신이 맞습니다 : subscribe ((result) => {}, (error) => {console.log (error.status);}. 오류 매개 변수는 여전히 Response 유형입니다.
— abedurftig

9

Angular 4.3 이상

길버트 아레나스 대거 를 완료하려면 답변 :

필요한 것이 오류를 가로 채고 처리를 적용하고 체인 아래로 전달하는 것 (단지를 사용하여 부작용을 추가하는 것이 아님 .do)이면 HttpClient 와 해당 인터셉터를 사용 하여 다음과 같은 작업을 수행 할 수 있습니다 .

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'));
        });
    }
}

9

"Router"와 같은 서비스가 Http 파생 클래스에 주입되어 발생하는 순환 참조 문제를 방지하려면 post-constructor Injector 메서드를 사용해야합니다. 다음 코드는 REST API가 "Token_Expired"를 반환 할 때마다 로그인 경로로 리디렉션하는 Http 서비스의 작동 구현입니다. 일반 Http에 대한 대체로 사용할 수 있으므로 애플리케이션의 기존 구성 요소 또는 서비스를 변경할 필요가 없습니다.

app.module.ts

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

확장 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());
  }
}


8

Angular> = 2.3.0 에서HTTP 모듈을 서비스를 삽입 할 수 있습니다. 버전 2.3.0 이전에는 핵심 버그로 인해 주입 된 서비스를 사용할 수 없었습니다.

나는 그것이 어떻게 이루어 졌는지 보여주기 위해 요점 을 만들었 습니다.


함께 해주셔서 감사합니다. app.module.ts에서 " 'Http'라는 이름을 찾을 수 없습니다"라는 빌드 오류가 발생하여 가져 왔고 이제 다음 오류가 발생합니다. "순환 종속성을 인스턴스화 할 수 없습니다! Http : in NgModule AppModule"
— Bryan

안녕하세요 @ Brett- app.module코드 를 공유 할 수 있습니까? 감사.
— mrgoos

괜찮아 보인다. 확장 된 HTTP를 요점에 추가 할 수 있습니까? 또한 HTTP다른 곳으로 가져 오나요?
— mrgoos

지연 돼서 죄송합니다. 현재 Angular 2.4를 사용 중이며 동일한 오류가 발생합니다. 여러 파일에서 Http를 가져옵니다. 업데이트 된 요점은 다음과 같습니다. gist.github.com/anonymous/606d092cac5b0eb7f48c9a357cd150c3
— Bryan

여기에 같은 문제가 있습니다 ...이 요점이 작동하지 않는 것 같습니다. 그래서 우리가 그렇게 표시해야할까요?
— Tuthmosis

2

Angular> 4.3 : 기본 서비스에 대한 ErrorHandler

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);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.