2

アクセス トークンを使用する API の Angular 2 フロントエンドを作成しています。これでオブザーバブルとngrx/storeを使用しようとしています。

ログインとログアウトは正常に機能し、意図したとおりに機能します。トークンの有効期限が切れたためにリクエストが失敗した場合のコードもいくつか書きました。このコードは通常は問題なく動作しますが、短期間に複数のリクエストがあると問題が発生します。これは、たとえば、ページを更新し、アプリがアプリ全体で必要な 2 つまたは 3 つのストアを埋めようとしたときに発生します。


私の認証サービスには、次のような機能があります。

refreshLogin(): Observable<any> {
    const username = this.currentUser();
    let query = new URLSearchParams();
    query.set('refresh_token', this.refreshToken());
    query.set('grant_type', 'refresh_token');
    return this.getToken(username, query.toString());
}

private getToken(username: string, body: string): Observable<any> {
    const url = Config.AUTH_TOKEN_PATH;
    return this.http.post(url, body)
        .map((res: Response) => res.json())
        .do(
            (data) => {
                const token = {
                    username: username,
                    accessToken: data.access_token,
                    refreshToken: data.refresh_token,
                    tokenType: data.token_type,
                    expiresIn: data.expires_in
                };
                this.store.dispatch(AuthActions.loginSuccess(token));
            },
            error => {
                const description = error.json().error_description;
                this.store.dispatch(AuthActions.loginError(description));
                console.error(error);
            }
        )
    ;
}

私の REST 関数には次のような関数があります。

get(url: string): Observable<any> {
    return this.http.get(url, new RequestOptions({ headers: this.authHeader() }))
        .catch(err => {
            if (err.status === 401) {
                return this.auth.refreshLogin()
                    .switchMap(() => this.http.get(url, new RequestOptions({ headers: this.authHeader() })))
                    .catch(err2 => err2);
            } else {
                console.log(err);
            }                
        })
        .map((res: Response) => res.json())
    ;
}

currentUser()関数、refreshToken()、およびauthHeader()が私の質問を理解するために必要だとは思いません。


失敗したリクエストが 1 つあり、エラーが 401 の場合、アプリは を呼び出しrefreshLogin()、新しいアクセス トークンを取得して保存し、新しいアクセス トークンを使用して元のリクエストを再試行します。

失敗したリクエストが複数あり、それらが同時に効果的に発生している場合、問題が発生します。たとえば、2 つの GET リクエストがあるとします。どちらも 401 エラーを返します。どちらもrefreshLogin()関数を起動します。1 つrefreshLogin()は成功し、新しいアクセス トークンを保存します。もう 1 つは、更新に使用されているトークンが無効になっているため失敗します。この一連の関数は失敗し、アプリが停止します。

1 つの解決策は、GET 要求を連続してスタックすることですが、そうする必要はないようです。

失敗した GET (またはその他の) 要求で、アプリがアクセス トークンを更新するための呼び出しをトリガーするソリューションが必要だと思います。認証サービスはこれらのリクエストを抑制して、数秒ごとに 1 つだけになるようにします。このリクエストが実行され、新しいアクセス トークンがすべてのリクエストに返され、再試行されます。

これは賢明なアプローチだと思いますか、それとも最初からよく考えられなかったアプローチを修正しようとしているだけですか? これらのパーツをどのように相互作用させることをお勧めしますか?

4

1 に答える 1

0

この問題を解決するために、トークンを更新するための新しい @ngrx/store アクションを作成し、@ngrx/effects を使用してそれを消費しました。誰もがエフェクトを使いたがるわけではないことは理解していますが、これだけでなく多くのシナリオで非常に役立つことがわかりました。

したがって、REST get 関数は次のようになります。

get(url: string): Observable<any> {
    return this.http.get(url, new RequestOptions({ headers: this.authHeader() }))
        .catch(err => {
            if (err.status === 401) {
                this.store.dispatch({type: 'REFRESH TOKEN'});
            }
            return Observable.of(err);
        })
        .map((res: Response) => res.json())
    ;
}

このアクションは、エフェクト モジュールによって取得されます...

@Effect() refreshToken$ = this.actions$
    .ofType('REFRESH TOKEN')
    .throttleTime(3000)
    .switchMap(() => this.authService.refreshLogin())
    .map((response) => {
        // Store token
    })

一方、REST get リクエストからのレスポンスを受け取った関数/アクション/whatever は、リクエストが成功したか、認証の失敗により失敗したかを判断できます。後者の場合は、(更新されたトークンを待ってから) もう一度リクエストを送信できます。それ以外の場合は、別の方法で別の種類の障害に対処できます。

于 2016-11-22T16:05:15.437 に答える