[英]How to refresh ACCESS-TOKEN in Retrofit2/rxJava
我發出請求(任何,授權,注冊等),然后才發現我需要更新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
是request 1
,接收TOKEN
的請求是query 2
。
問題 :如果在query 1
出現錯誤,並且query 2
成功后返回到query 1
,如何更新TOKEN
?
我不確定您如何收到新令牌,因為getAccessToken()
的返回類型是Single<UserProfile>
。 我想應該改為Single<String>
。 也許不是這種情況,您會在標頭中或作為UserProfile
的字段收到令牌。 無論哪種情況,您都可以從以下解決方案中得到一個想法,並根據情況進行調整。
該方法是,我們從使用令牌存儲的原始數據庫中創建一個新的可觀察對象,該令牌存儲包含最新的令牌。 我們使用compose
和onErrorResumeNext
處理401錯誤,以便發出令牌刷新請求,將新令牌保存到令牌存儲中,並使用新令牌重試原始請求。
有關更詳細的說明,請參見下面的代碼中的注釋:
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(將令牌存儲在字段中),也可以將令牌存儲在共享的首選項中,以便令牌在應用程序重啟后繼續存在。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.