web-dev-qa-db-ja.com

rxjsを使用した更新トークンの処理

Angular2で始めたので、TのObservableを返すようにサービスを設定しました。サービスではmap()呼び出しがあり、これらのサービスを使用するコンポーネントはsubscribe()を使用して応答を待機します。これらの単純なシナリオでは、rxjsを実際に調べる必要はなかったので、すべて問題ありませんでした。

今、私は以下を達成したいと思います:リフレッシュトークンでOauth2認証を使用しています。他のすべてのサービスが使用するAPIサービスを構築し、401エラーが返されたときに更新トークンを透過的に処理します。そのため、401の場合、最初にOAuth2エンドポイントから新しいトークンを取得してから、新しいトークンでリクエストを再試行します。以下は、約束された状態で正常に動作するコードです。

request(url: string, request: RequestOptionsArgs): Promise<Response> {
    var me = this;

    request.headers = request.headers || new Headers();
    var isSecureCall: boolean =  true; //url.toLowerCase().startsWith('https://');
    if (isSecureCall === true) {
        me.authService.setAuthorizationHeader(request.headers);
    }
    request.headers.append('Content-Type', 'application/json');
    request.headers.append('Accept', 'application/json');

    return this.http.request(url, request).toPromise()
        .catch(initialError => {
            if (initialError && initialError.status === 401 && isSecureCall === true) {
                // token might be expired, try to refresh token. 
                return me.authService.refreshAuthentication().then((authenticationResult:AuthenticationResult) => {
                    if (authenticationResult.IsAuthenticated == true) {
                        // retry with new token
                        me.authService.setAuthorizationHeader(request.headers);
                        return this.http.request(url, request).toPromise();
                    }
                    return <any>Promise.reject(initialError);
                });
            }
            else {
                return <any>Promise.reject(initialError);
            }
        });
}

上記のコードでは、authService.refreshAuthentication()は新しいトークンを取得してlocalStorageに保存します。 authService.setAuthorizationHeaderは、「Authorization」ヘッダーを以前に更新されたトークンに設定します。 catchメソッドを見ると、(リフレッシュトークンの)promiseが返され、順番に(実際の2回目のリクエストの)別のpromiseが返されることがわかります。

私は約束に頼らずにこれをやろうとしました:

request(url: string, request: RequestOptionsArgs): Observable<Response> {
    var me = this;

    request.headers = request.headers || new Headers();
    var isSecureCall: boolean =  true; //url.toLowerCase().startsWith('https://');
    if (isSecureCall === true) {
        me.authService.setAuthorizationHeader(request.headers);
    }
    request.headers.append('Content-Type', 'application/json');
    request.headers.append('Accept', 'application/json');

    return this.http.request(url, request)
        .catch(initialError => {
            if (initialError && initialError.status === 401 && isSecureCall === true) {
                // token might be expired, try to refresh token
                return me.authService.refreshAuthenticationObservable().map((authenticationResult:AuthenticationResult) => {
                    if (authenticationResult.IsAuthenticated == true) {
                        // retry with new token
                        me.authService.setAuthorizationHeader(request.headers);
                        return this.http.request(url, request);
                    }
                    return Observable.throw(initialError);
                });
            }
            else {
                return Observable.throw(initialError);
            }
        });
}

上記のコードは私が期待することをしません:200応答の場合、それは適切に応答を返します。ただし、401をキャッチすると、新しいトークンは正常に取得されますが、サブスクライブは最終的に応答ではなくオブザーバブルを取得します。これは、再試行を行う必要がある未実行のObservableであると推測しています。

約束の方法をrxjsライブラリに翻訳するのはおそらく最善の方法ではないことを理解していますが、「すべてがストリーム」であることを理解できていません。私は、flatmap、retryWhenなどを含む他のいくつかのソリューションを試してみましたが、うまくいきませんでした。

43
Davy

あなたのコードをざっと見てみると、あなたの問題は、あなたがObservableサービスから返されたrefreshを平坦化していないということだと思います。

catch演算子は、ダウンストリームObservableが違いを認識しないように、失敗したObservableの最後に連結するObserverを返すことを期待しています。

401以外の場合、初期エラーを再スローするObservableを返すことにより、これを正しく実行しています。ただし、更新の場合は、Observableを返すため、単一の値ではなくmoreObservablesが生成されます。

リフレッシュロジックを次のように変更することをお勧めします。

    return me.authService
             .refreshAuthenticationObservable()
             //Use flatMap instead of map
             .flatMap((authenticationResult:AuthenticationResult) => {
                   if (authenticationResult.IsAuthenticated == true) {
                     // retry with new token
                     me.authService.setAuthorizationHeader(request.headers);
                     return this.http.request(url, request);
                   }
                   return Observable.throw(initialError);
    });

flatMapは、中間のObservablesを単一のストリームに変換します。

24
paulpdaniels

RxJsの最新リリースでは、flatMap演算子の名前がmergeMapに変更されました。

11
mostefaiamine

これを作成して demo rxjsを使用して更新トークンを処理する方法を見つけました。これを行います:

  • アクセストークンを使用してAPI呼び出しを行います。
  • アクセストークンは、(観測可能で、適切なエラーがスローされます)有効期限が切れた場合は、トークンをリフレッシュするために別の非同期呼び出しを行います。
  • トークンが更新されると、API呼び出しが再試行されます。
  • それでもエラーの場合は、あきらめます。

このデモは、それが使用してそれらをシミュレートし(実際のHTTP呼び出しは行いませんObservable.create)。

代わりに、catchErrorおよびretry演算子を使用して問題を解決し(アクセストークンが最初に失敗した)、失敗した操作(API呼び出し)を再試行する方法を学習します。

2
kctang