首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在Retro菲特2/rxJava中刷新访问令牌

如何在Retro菲特2/rxJava中刷新访问令牌
EN

Stack Overflow用户
提问于 2017-12-27 16:56:46
回答 1查看 868关注 0票数 1

本人提出要求(任何、授权、登记等)。然后我才发现我需要更新ACCESS-TOKEN,也就是说,我得到了错误401

以下是授权请求:

代码语言:javascript
复制
BaseApplication.getApiClient()
            .signIn(accessToken, body)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new SingleObserver<UserProfile>() {
                @Override
                public void onSubscribe(Disposable d) {
                    Log.d("-- SignInOnSubscribe", "Subscribed!");
                }

                @Override
                public void onSuccess(UserProfile userProfile) {
                    if (userProfile.getErrorDetails() != null) {
                        onSignInFinishedCallback.onLoginFailure(userProfile.getErrorDetails());
                        Log.d("-- SignInOnError", userProfile.getErrorDetails());
                    } else {
                        onSignInFinishedCallback.onLoginSuccess(userProfile);
                        profileRepository.updateUserProfile(userProfile);

                        Log.d("-- SignInOnSuccess", userProfile.getName());
                    }
                }

                @Override
                public void onError(Throwable e) {
                    Log.d("-- SignInOnError", e.getMessage());

                    if (e.getMessage().equals(Constants.CODE_UNAUTHORIZED)){
                        // Action on error 401
                    }

                    onSignInFinishedCallback.onLoginFailure(e.getMessage());
                }
            });

API请求:

代码语言:javascript
复制
@POST("/api/login")
Single<UserProfile> getAccessToken(@Body Map<String, String> requestBody);

@POST("/api/abonent/login")
Single<UserProfile> signIn(@Header("X-ACCESS-TOKEN") String accessToken,
                             @Body Map<String, String> requestBody);

例如,授权请求是request 1,接收TOKEN的请求是query 2

问题:如果在query 1中得到一个错误,并且在query 2成功之后,返回执行query 1,我如何更新query 1

EN

回答 1

Stack Overflow用户

发布于 2017-12-31 09:25:58

我不确定您是如何接收新令牌的,因为返回类型的getAccessToken()Single<UserProfile>。我想应该是Single<String>。也许情况并非如此,您可以在标头中或作为UserProfile字段接收令牌。在任何一种情况下,您都可以从下面的解决方案中获得一个想法,并将其调整到您的情况。

我们的方法是从原始的令牌存储库中创建一个可观察到的新的令牌存储,该存储存储了最新的令牌。我们使用composeonErrorResumeNext处理401错误,以便发出令牌刷新请求,将新令牌保存到令牌存储中,并使用新令牌重试原始请求。

有关更详细的解释,请参见下面代码中的注释:

代码语言:javascript
复制
public void signIn(final Map<String, String> body) {
    Single
            // Wrap the original request with a "defer" so that the access token is
            // evaluated each time it is called. This is important because the refreshed
            // access token should be used the second time around.
            .defer(new Callable<SingleSource<UserProfile>>() {
                @Override
                public SingleSource<UserProfile> call() throws Exception {
                    return BaseApplication.getApiClient()
                            .signIn(accessTokenStore.getAccessToken(), body);
                }
            })
            // Compose it with a transformer that refreshes the token in the token store and
            // retries the original request, this time with the refreshed token.
            .compose(retryOnNotAuthorized(body))

            // The code remains the same from here.
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new SingleObserver<UserProfile>() {
                @Override
                public void onSubscribe(Disposable d) {
                    Log.d("-- SignInOnSubscribe", "Subscribed!");
                }

                @Override
                public void onSuccess(UserProfile userProfile) {
                    if (userProfile.getErrorDetails() != null) {
                        onSignInFinishedCallback.onLoginFailure(userProfile.getErrorDetails());
                        Log.d("-- SignInOnError", userProfile.getErrorDetails());
                    } else {
                        onSignInFinishedCallback.onLoginSuccess(userProfile);
                        profileRepository.updateUserProfile(userProfile);

                        Log.d("-- SignInOnSuccess", userProfile.getName());
                    }
                }

                @Override
                public void onError(Throwable e) {
                    Log.d("-- SignInOnError", e.getMessage());

                    if (e.getMessage().equals(Constants.CODE_UNAUTHORIZED)) {
                        // Action on error 401
                    }

                    onSignInFinishedCallback.onLoginFailure(e.getMessage());
                }
            });
}

@NonNull
private SingleTransformer<UserProfile, UserProfile> retryOnNotAuthorized(final Map<String, String> body) {
    return new SingleTransformer<UserProfile, UserProfile>() {
        @Override
        public SingleSource<UserProfile> apply(final Single<UserProfile> upstream) {
            // We use onErrorResumeNext to continue our Single stream with the token refresh
            // and the retrial of the request.
            return upstream.onErrorResumeNext(new Function<Throwable, SingleSource<? extends UserProfile>>() {
                @Override
                public SingleSource<UserProfile> apply(Throwable throwable) throws Exception {
                    if (throwable instanceof HttpException
                            && ((HttpException) throwable).code() == 401) {
                        return BaseApplication.getApiClient().getAccessToken(body)
                                // I always use doOnSuccess() for non-Rx side effects, such as caching the token.
                                // I think it's clearer than doing the caching in a map() or flatMap().
                                .doOnSuccess(new Consumer<String>() {
                                    @Override
                                    public void accept(String accessToken) throws Exception {
                                        // Save the access token to the store for later use.
                                        accessTokenStore.storeAccessToken(accessToken);
                                    }
                                })
                                // We don't need the result of getAccessToken() any more, so I
                                // think it's cleaner to convert the stream to a Completable.
                                .toCompletable()

                                // After the token is refreshed and stored, the original request
                                // should be repeated.
                                .andThen(upstream);
                    }

                    // If the error was not 401, pass through the original error
                    return Single.error(throwable);
                }
            });
        }
    };
}

更新:令牌存储只是一个带有get和存储方法的常规接口。您应该将其实现为POJO (将令牌存储在字段中),也可以将令牌存储在共享首选项中,这样令牌才能在应用程序重新启动时存活下来。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47995891

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档