【问题标题】:How to refresh ACCESS-TOKEN in Retrofit2/rxJava如何在 Retrofit2/rxJava 中刷新 ACCESS-TOKEN
【发布时间】:2017-12-27 16:56:46
【问题描述】:

我提出一个请求(任何、授权、注册等),然后我才发现我需要更新ACCESS-TOKEN,即我收到错误401

这是授权请求:

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 请求:

@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 成功后,我如何更新TOKEN,然后再执行query 1

【问题讨论】:

    标签: java android rx-java retrofit2


    【解决方案1】:

    我不确定您是如何收到新令牌的,因为getAccessToken() 的返回类型是Single&lt;UserProfile&gt;。我想应该是Single&lt;String&gt;。也许情况并非如此,您会在标头或UserProfile 的字段中收到令牌。无论哪种情况,您都可以从以下解决方案中获得一个想法并根据您的情况进行调整。

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

    更详细的解释见下面代码中的cmets:

    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 和 store 方法的常规接口。您应该将其实现为 POJO(将令牌存储在字段中),或者您可以将令牌存储在共享首选项中,以便令牌在应用重新启动后仍然存在。

    【讨论】:

      猜你喜欢
      • 2019-05-02
      • 2019-11-20
      • 1970-01-01
      • 2016-11-05
      • 2016-03-24
      • 2022-01-04
      • 2014-04-20
      • 1970-01-01
      • 2023-03-23
      相关资源
      最近更新 更多