代码之家  ›  专栏  ›  技术社区  ›  Davy

使用rxjs处理刷新令牌

  •  47
  • Davy  · 技术社区  · 10 年前

    自从我开始使用angular2以来,我已经将服务设置为返回T的Observable。在服务中,我将使用map()调用,使用这些服务的组件将只使用subscribe()等待响应。对于这些简单的场景,我并不需要深入研究rxjs,所以一切都很好。

    我现在想实现以下目标:我正在使用Oauth2身份验证和刷新令牌。我想构建一个所有其他服务都将使用的api服务,当返回401错误时,它将透明地处理刷新令牌。因此,在401的情况下,我首先从OAuth2端点获取一个新令牌,然后使用新令牌重试我的请求。下面是工作良好的代码,带有承诺:

    request(url: string, request: RequestOptionsArgs): Promise<Response> {
        var me = this;
    
        request.headers = request.headers || new Headers();
        var isSecureCall: boolean =  true; //url.toLowerCase().startsWith('https://');
        if (isSecureCall === true) {
            me.authService.setAuthorizationHeader(request.headers);
        }
        request.headers.append('Content-Type', 'application/json');
        request.headers.append('Accept', 'application/json');
    
        return this.http.request(url, request).toPromise()
            .catch(initialError => {
                if (initialError && initialError.status === 401 && isSecureCall === true) {
                    // token might be expired, try to refresh token. 
                    return me.authService.refreshAuthentication().then((authenticationResult:AuthenticationResult) => {
                        if (authenticationResult.IsAuthenticated == true) {
                            // retry with new token
                            me.authService.setAuthorizationHeader(request.headers);
                            return this.http.request(url, request).toPromise();
                        }
                        return <any>Promise.reject(initialError);
                    });
                }
                else {
                    return <any>Promise.reject(initialError);
                }
            });
    }
    

    在上面的代码中,authService。refreshAuthentication()将获取新令牌并将其存储在localStorage中。authService。setAuthorizationHeader将把“Authorize”标头设置为先前更新的令牌。如果您查看catch方法,您会看到它返回一个promise(用于刷新令牌),该promise最终将返回另一个procese(用于请求的实际第二次尝试)。

    我试图做到这一点,而不诉诸承诺:

    request(url: string, request: RequestOptionsArgs): Observable<Response> {
        var me = this;
    
        request.headers = request.headers || new Headers();
        var isSecureCall: boolean =  true; //url.toLowerCase().startsWith('https://');
        if (isSecureCall === true) {
            me.authService.setAuthorizationHeader(request.headers);
        }
        request.headers.append('Content-Type', 'application/json');
        request.headers.append('Accept', 'application/json');
    
        return this.http.request(url, request)
            .catch(initialError => {
                if (initialError && initialError.status === 401 && isSecureCall === true) {
                    // token might be expired, try to refresh token
                    return me.authService.refreshAuthenticationObservable().map((authenticationResult:AuthenticationResult) => {
                        if (authenticationResult.IsAuthenticated == true) {
                            // retry with new token
                            me.authService.setAuthorizationHeader(request.headers);
                            return this.http.request(url, request);
                        }
                        return Observable.throw(initialError);
                    });
                }
                else {
                    return Observable.throw(initialError);
                }
            });
    }
    

    上面的代码没有达到我预期的效果:在200响应的情况下,它正确地返回了响应。然而,如果它捕捉到401,它将成功地检索到新令牌,但订阅最终将检索到可观察的而不是响应。我猜这是应该进行重试的未执行Observable。

    我意识到,将promise的工作方式翻译到rxjs库上可能不是最好的方法,但我还没能理解“一切都是流”这一点。我尝试了一些其他的解决方案,包括平面图、retryWhen等……但效果并不理想,因此我们非常感谢您的帮助。

    3 回复  |  直到 10 年前
        1
  •  25
  •   paulpdaniels    10 年前

    通过快速查看您的代码,我可以说您的问题似乎是您没有将 Observable 从 refresh 服务

    这个 catch 操作员希望您返回 可观察的 它将连接到失败的Observable的末尾 Observer 不知道区别。

    在非401的情况下,您通过返回一个Observable来正确地执行此操作,该Observatable会重新引发初始错误。但是,在刷新情况下,您将返回 可观察的 产品 更多 Observables 而不是单个值。

    我建议您将刷新逻辑更改为:

        return me.authService
                 .refreshAuthenticationObservable()
                 //Use flatMap instead of map
                 .flatMap((authenticationResult:AuthenticationResult) => {
                       if (authenticationResult.IsAuthenticated == true) {
                         // retry with new token
                         me.authService.setAuthorizationHeader(request.headers);
                         return this.http.request(url, request);
                       }
                       return Observable.throw(initialError);
        });
    

    flatMap 将转换中间 可观测值 进入单个流。

        2
  •  12
  •   msanford    9 年前

    在最新版本的RxJ中 flatMap 运算符已重命名为 mergeMap .

        3
  •  2
  •   kctang    8 年前

    我创建了这个 demo 了解如何使用rxjs处理刷新令牌。它这样做:

    • 使用访问令牌进行API调用。
    • 如果访问令牌过期(observable抛出适当的错误),它将进行另一个异步调用以刷新令牌。
    • 令牌刷新后,它将重试API调用。
    • 如果仍然出错,请放弃。

    这个演示没有进行实际的HTTP调用(它使用 Observable.create ).

    相反,用它来学习如何使用 catchError 和 retry 操作员修复问题(访问令牌第一次失败),然后重试失败的操作(API调用)。