Angular 2处理需要身份验证令牌刷新的多个失败请求

时间:2016-09-26 10:41:15

标签: authentication angular angular2-observables

我正在为使用访问令牌的API创建一个Angular 2前端。我正在尝试使用observables和ngrx / store。

登录和退出工作正常并且符合预期。我还写了一些代码,用于在令牌到期时请求失败。这段代码通常可以正常工作,但是当我在很短的时间内有多个请求时,我遇到了麻烦。例如,当我刷新页面并且应用程序尝试在应用程序中填充所需的两个或三个商店时,会发生这种情况。

我的身份验证服务具有以下功能:

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()是理解我的问题所必需的。

如果我有一个失败的请求且错误是401,我的应用程序调用{​​{1}},获取一个新的访问令牌并存储它,并使用新的访问令牌再次尝试原始请求。

如果我有更多的失败请求,并且它们同时有效地发生,我会遇到问题。例如,假设我有两个GET请求。他们都返回401错误。它们都触发了refreshLogin()功能。一个refreshLogin()成功并存储新的访问令牌;另一个失败,因为用于刷新的令牌不再有效。这个函数流现在失败了,导致我的应用程序停止运行。

一种解决方案是将我的GET请求串联起来,但看起来不应该是这样。

我觉得应该有一个解决方案,在失败的GET(或其他)请求中,应用程序触发对刷新访问令牌的调用。 auth服务会限制这些请求,因此每隔几秒钟只能有一个请求。它满足此请求,并将新的访问令牌返回给所有请求以再次尝试。

你认为这是一种明智的做法,还是我只想尝试修补一个经过深思熟虑的方法?您如何建议使这些部件互动?

1 个答案:

答案 0 :(得分: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
    })

同时,函数/ action /从REST get请求接收响应的任何内容都可以确定请求是否成功或者由于身份验证失败而失败。如果是后者,它可以再次发出请求(在等待续订令牌之后);否则,它可以以不同的方式处理另一种类型的失败。