我正在为一个使用访问令牌的API做一个Angular 2前端。我正在尝试使用observable和ngrx/store。
登录和退出工作正常,如预期。我还编写了一些代码,用于在令牌到期时请求失败。这段代码正常工作,但当我在短时间内有多个请求时,我遇到了麻烦。例如,当我刷新页面时,应用程序会尝试填充整个应用程序所需的两到三个存储。
我的auth服务有如下功能:
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,我的应用程序调用refreshLogin()
,获得一个新的访问令牌并存储它,并且使用新的访问令牌再次尝试原始请求。
如果我有多个失败的请求,并且它们同时有效地发生,我就会遇到问题。例如,假设我有两个GET请求。它们都返回401个错误。它们都启动了refreshLogin()
功能。一个refreshLogin()
成功并存储一个新的访问令牌;另一个失败是因为用于刷新的令牌不再有效。这个函数流现在失败了,导致我的应用程序停止运行。
一个解决方案是将我的GET请求按顺序堆叠,但这似乎不应该是这种情况。
我觉得应该有一个解决方案,在一个失败的GET(或其他)请求,应用程序触发访问令牌被刷新的调用。验证服务将限制这些请求,因此每隔几秒钟只能有一个请求。它满足此请求,并将新的访问令牌返回给所有请求以再次尝试。
你认为这是一个明智的方法吗?还是我只是想修补一个一开始就考虑不周的方法?您建议如何使这些部件相互作用?
为了解决这个问题,我创建了一个新的@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请求接收响应的函数/动作/任何可以确定请求是否成功,或者是否由于身份验证失败而失败。如果是后者,它可以在另一次触发请求(在等待更新的令牌之后);否则,它可以用不同的方式处理另一种类型的故障。