Angular 4 http拦截器refresh_token触发次数太多

时间:2017-06-30 10:07:48

标签: angular typescript oauth rxjs

我正在尝试在角度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对我来说应该是这样,但遗憾的是它们都会在我的令牌刷新之前触发请求。

有关如何实现这一目标的任何想法?

1 个答案:

答案 0 :(得分:2)

在@ stely000,一些同事和其他在线社区的帮助下,我结束了这个:

由于我使用解决方案拦截来自angular2 / angular4的内置Http服务https://scotch.io/@kashyapmukkamala/using-http-interceptor-with-angular2,我的代码与我发现的其他解决方案略有不同。我可以在此处找到OAuthService我在不同地方引用的Httphttps://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(); } 功能触发一次的解决方案分三步实现:

  1. 创建一个共享的observable以避免 this.postRequest()一次触发多次。

  2. 创建请求标头并将帖子添加到端点位置 处理refresh_token。

  3. 从第1步收听共享观察者。当令牌 已在本地存储中刷新,提取和更新数据。

  4. 现在,在我的构造函数中,我创建了共享的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和其他社区)。