我正在尝试在角度Http中修饰内置的request
方法。
我同时触发大量链接和/或异步请求,这反过来导致401响应同时被触发很多次。这会导致授权崩溃,因为多次使用相同的刷新令牌。
到目前为止,我已经走到了这一步:
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return super.request(url, options).catch(error=> {
if (error.status === 401) {
return Observable.create((observer: Observer<Response>) => {
// Use promise to avoid OPTIONS request
this.oauthService.refreshToken().then((tokenResponse: Response) => {
console.debug('promise resolved');
observer.next(tokenResponse);
observer.complete();
});
}).mergeMap((tokenResponse) => {
options = this.updateAuthHeader(options);
return super.request(url, options);
});
} else {
return Observable.throw(error);
}
});
}
我的问题似乎是我需要在遇到401时将待处理的请求排队,或者我只是缺乏如何使用observable实现这一点的知识。据我所知,mergeMap
对我来说应该是这样,但遗憾的是它们都会在我的令牌刷新之前触发请求。
有关如何实现这一目标的任何想法?
答案 0 :(得分:2)
在@ stely000,一些同事和其他在线社区的帮助下,我结束了这个:
由于我使用解决方案拦截来自angular2 / angular4的内置Http服务https://scotch.io/@kashyapmukkamala/using-http-interceptor-with-angular2,我的代码与我发现的其他解决方案略有不同。我可以在此处找到OAuthService
我在不同地方引用的Http
:https://github.com/manfredsteyer/angular-oauth2-oidc。由于该服务对refresh_token
的依赖性,我必须注入该服务以避免循环依赖。如果您对此有任何疑问,请问我,我也会回答。 :)
基本上,如果后端服务响应401,我的constructor(
backend: ConnectionBackend,
defaultOptions: RequestOptions,
private injector: Injector
) {
super(backend, defaultOptions);
// Step 1: Create an observable that is shared to avoid this.postRequest() to trigger more than once at a time
this.refreshTokenObserver = Observable.defer(() => {
return this.postRequest();
}).share();
}
功能触发一次的解决方案分三步实现:
创建一个共享的observable以避免 this.postRequest()一次触发多次。
创建请求标头并将帖子添加到端点位置 处理refresh_token。
从第1步收听共享观察者。当令牌 已在本地存储中刷新,提取和更新数据。
现在,在我的构造函数中,我创建了共享的observable:
access_token
然后我创建了一个发布请求以刷新OAuthService
的方法(注意:这基本上是// This method will only be triggered once at a time thanks to she shared observer above (Step 1).
private postRequest(): Observable<any> {
// Step 2: Create request headers and add post to endpoint where refresh_token is handled.
let search = new URLSearchParams();
search.set('grant_type', 'refresh_token');
search.set('client_id', this.oauthService.clientId);
search.set('scope', '');
search.set('refresh_token', localStorage.getItem('refresh_token'));
let headers = new Headers();
headers.set('Content-Type', 'application/x-www-form-urlencoded');
let params = search.toString();
return super.post(this.oauthService.tokenEndpoint, params, { headers }).map(r => r.json());
}
中保存的代码的副本。这是因为方法是在该服务中不公开):
// This method is triggered when the server responds with 401 due to expired access_token or other reasons
private refreshToken() {
// Step 3: Listen to the shared observer from step 1. When the token has been refreshed, extract and update data in localstorage
return this.refreshTokenObserver.do((tokenResponse) => {
localStorage.setItem("access_token", tokenResponse.access_token);
if (tokenResponse.expires_in) {
var expiresInMilliSeconds = tokenResponse.expires_in * 1000;
var now = new Date();
var expiresAt = now.getTime() + expiresInMilliSeconds;
localStorage.setItem("expires_at", "" + expiresAt);
}
if (tokenResponse.refresh_token) {
localStorage.setItem("refresh_token", tokenResponse.refresh_token);
}
},
(err) => {
console.error('Error performing password flow', err);
return Observable.throw(err);
});
}
然后我有一个方法来提取数据并用当时端点的响应更新本地存储:
401
要启动上述步骤,需要触发初始请求并使用request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return super.request(url, options).catch(error=> {
if (error.status === 401) {
// It the token has been expired trigger a refresh and after that continue the original request again with updated authorization headers.
return this.refreshToken().mergeMap(() => {
options = this.updateAuthHeader(options);
return super.request(url, options);
});
} else {
return Observable.throw(error);
}
});
}
进行回复:
OAuthService
奖励:我用于更新授权标头的方法基本上是使用上面提到的private updateAuthHeader(options: RequestOptionsArgs) {
options.headers.set('Authorization', this.oauthService.authorizationHeader());
return options;
}
中的功能:
OAuthService
<强>思考/思想:强>
我最初的想法是使用postRequest
来刷新令牌。由于承诺和可观察性的混合,这比我预期的要困难。我可以更改#ex.1
print ' '.join(map(str, board[:3]))
#ex.2
print ' '.join(str(board[:3]))
#out.1
0 1 2
#out.2
[ 0 , 1 , 2 ]
方法以使用上述服务方法。我真的不知道更好/更清洁的解决方案是什么。
此外,我认为这应该是每个人都可以找到一个简单的解决方案。这很难自己实现,我感谢所有帮助我的人(在SO,IRL和其他社区)。