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 응답 상태 401403특정 작업에 리드를.

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 FINALRxJS 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.