首页 文章

使用rxjs处理刷新令牌

提问于
浏览
41

由于我已经开始使用angular2,我已经设置了我的服务来返回T的Observable . 在服务中我会有map()调用,使用这些服务的组件只会使用subscribe()来等待响应 . 对于这些简单的场景我真的不需要深入研究rxjs所以一切都还可以 .

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

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将“授权”标头设置为先前更新的标记 . 如果你看一下catch方法,你会看到它返回一个promise(对于刷新令牌),它将最终返回另一个promise(对于请求的实际第二次尝试) .

我试图在不诉诸承诺的情况下这样做:

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而不是响应 . 我猜这是应该进行重试的未执行的Observable .

我意识到将工作的承诺方式转换为rxjs库可能不是最好的方法,但我还没有能够掌握“一切都是流”的事情 . 我尝试了一些涉及flatmap,retryWhen等的其他解决方案......但是没有做得太远,所以一些帮助表示赞赏 .

3 回答

  • 1

    从快速查看代码我会说你的问题似乎是你没有弄平 refresh 服务返回的 Observable .

    catch 运算符期望您将返回 Observable ,它将连接到失败的Observable的末尾,以便下游 Observer 不知道差异 .

    在非401情况下,通过返回重新抛出初始错误的Observable,您正确地执行此操作 . 但是,在刷新的情况下,返回 Observable 会产生更多 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 将中间 Observables 转换为单个流 .

  • 10

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

  • 23

    我创建了这个demo来弄清楚如何使用rxjs处理刷新令牌 . 它这样做:

    • 使用访问令牌进行API调用 .

    • 如果访问令牌过期(observable抛出相应的错误),它会进行另一次异步调用以刷新令牌 .

    • 刷新令牌后,它将重试API调用 .

    • 如果仍然出错,请放弃 .

    此演示不会进行实际的HTTP调用(它使用 Observable.create 模拟它们) .

    相反,使用它来学习如何使用 catchErrorretry 运算符来修复问题(第一次访问令牌失败),然后重试失败的操作(API调用) .

相关问题