1. ホーム
  2. angular

[解決済み] Angularでグローバルに401を処理する

2023-03-04 07:05:35

質問

私のAngular 2プロジェクトでは、Observableを返すサービスからAPIコールを行います。そして、呼び出しコードはこのobservableを購読します。たとえば、次のようになります。

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 is not a function".と表示されます。

どのように解決するのですか?

説明

私が見つけた最良の解決法は XHRBackend をオーバーライドして、HTTP レスポンス・ステータスが 401403 は特定のアクションを導きます。

Angularアプリケーションの外側で認証を処理する場合、外部メカニズムがトリガーされるように現在のページを強制的にリフレッシュすることができます。私はこの解決策を以下の実装で詳しく説明します。

また、Angularアプリケーションがリロードされないように、アプリケーション内部のコンポーネントに転送することもできます。

実装

アンギュラ 2.3.0

mrgoos のおかげで、angular 2.3.0 のバグ修正により、angular 2.3.0+ 用の簡略化されたソリューションがあります(issue 参照 https://github.com/angular/angular/issues/11606 を直接拡張しています。 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 }
]

Routerと外部認証サービスを利用した別のソリューションの詳細は以下の通りです。 要旨 をご覧ください。

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