使用 rxjs 处理刷新令牌 [英] Handling refresh tokens using rxjs

查看:20
本文介绍了使用 rxjs 处理刷新令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Since i've started with angular2 i have setup my services to return Observable of T. In the service i would have the map() call, and components using these services would just use subscribe() to wait for the response. For these simple scenarios i didnt really need to dig in to rxjs so all was ok.

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

I now want to achieve the following: i am using Oauth2 authentication with refresh tokens. I want to build an api service that all other services will use, and that will transparently handle the refresh token when a 401 error is returned. So, in the case of a 401, i first fetch a new token from the OAuth2 endpoint, and then retry my request with the new token. Below is the code that works fine, with promises:

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 方法,您会看到它返回一个承诺(用于刷新令牌),该承诺最终将最终返回另一个承诺(用于请求的实际第二次尝试).

In the code above, authService.refreshAuthentication() will fetch the new token and store it in localStorage. authService.setAuthorizationHeader will set the 'Authorization' header to previously updated token. If you look at the catch method, you'll see that it returns a promise (for the refresh token) that in its turns will eventually return another promise (for the actual 2nd try of the request).

我已经尝试在不诉诸承诺的情况下做到这一点:

I have attempted to do this without resorting to promises:

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.

The code above does not do what i expect: in the case of a 200 response, it properly returns the response. However, if it catches the 401, it will successfully retrieve the new token, but the subscribe wil eventually retrieve an observable instead of the response. Im guessing this is the unexecuted Observable that should do the retry.

我意识到将 promise 的工作方式转换到 rxjs 库上可能不是最好的方法,但我一直无法理解一切都是流"这件事.我已经尝试了其他一些涉及 flatmap、retryWhen 等的解决方案......但没有走多远,所以感谢一些帮助.

I realize that translating the promise way of working onto the rxjs library is probably not the best way to go, but i havent been able to grasp the "everything is a stream" thing. I have tried a few other solutions involving flatmap, retryWhen etc ... but didnt get far, so some help is appreciated.

推荐答案

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

From a quick look at your code I would say that your problem seems to be that you are not flattening the Observable that is returned from the refresh service.

catch 运算符期望您将返回一个 Observable,它将连接到失败的 Observable 的末尾,以便下游 Observer 不知道有什么区别.

The catch operator expects that you will return an Observable that it will concatenate onto the end of the failed Observable so that the down stream Observer doesn't know the difference.

在非 401 的情况下,您通过返回重新抛出初始错误的 Observable 来正确执行此操作.但是,在刷新的情况下,您将返回 Observable 产生 more Observables 而不是单个值.

In the non-401 case you are doing this correctly by returning an Observable that rethrows the initial error. However in the refresh case you are returning an Observable the produces more Observables instead of single values.

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

I would suggest you change the refresh logic to be:

    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 转换成单个流.

flatMap will convert the intermediate Observables into a single stream.

这篇关于使用 rxjs 处理刷新令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆