Этот подход всегда работал при обновлении token. То есть при каждом запросе, если я получил error 401, оператор retryWhen() запускал его, обновляя токен.
Вот код:
private Observable<TokenModel> refreshAccessToken() {
Map<String, String> requestBody = new HashMap<>();
requestBody.put(Constants.EMAIL_KEY, Constants.API_EMAIL);
requestBody.put(Constants.PASSWORD_KEY, Constants.API_PASSWORD);
return RetrofitHelper.getApiService().getAccessToken(requestBody)
.subscribeOn(Schedulers.io())
.doOnNext((AccessToken refreshedToken) -> {
PreferencesHelper.putAccessToken(mContext, refreshedToken);
});
}
public Function<Observable<Throwable>, ObservableSource<?>> isUnauthorized (){
return throwableObservable -> throwableObservable.flatMap((Function<Throwable, ObservableSource<?>>) (Throwable throwable) -> {
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
if (httpException.code() == 401) {
return refreshAccessToken();
}
}
return Observable.error(throwable);
});
}
Звоню isUnauthorized() оператору retryWhen(), где делаю запрос к серверу
class RetrofitHelper {
static ApiService getApiService() {
return initApi();
}
private static OkHttpClient createOkHttpClient() {
final OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(chain -> {
Request originalRequest = chain.request();
AccessToken accessToken= PreferencesHelper.getAccessToken(BaseApplication.getInstance());
String accessTokenStr = accessToken.getAccessToken();
Request.Builder builder =
originalRequest.newBuilder().header("Authorization", "Bearer " + accessTokenStr);
Request newRequest = builder.build();
return chain.proceed(newRequest);
});
return httpClient.build();
}
private static ApiService initApi(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants._api_url)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(createOkHttpClient())
.build();
return retrofit.create(ApiService.class);
}
}
Но недавно мы добавили Basic Auth, и теперь по первому запросу я получаю 401, а retryWhen() пытается обновить токен, но все равно получает 401. То есть doOnNext() не работает, а сразу onError() работает
private static Observable<AccessToken> refreshAccessToken() {
return RetrofitHelper.getApiService()
.getAccessToken(
Credentials.basic(
Constants._API_USERNAME, Constants._API_PASSWORD
),
Constants._API_BODY_USERNAME,
Constants._API_BODY_PASSWORD,
Constants._API_BODY_GRANT_TYPE
)
.doOnNext((AccessToken refreshedToken) -> {
PreferencesHelper.putObject(BaseApplication.getInstance(), PreferenceKey.ACCESS_TOKEN_KEY, refreshedToken);
}
});
}
// Служба API
public interface ApiService {
// Get Bearer Token
@FormUrlEncoded
@POST("oauth/token")
Observable<AccessToken> getAccessToken(@Header("Authorization") String basicAuth,
@Field("username") String username,
@Field("password") String password,
@Field("grant_type") String grantType);
}
Вот скажите, почему это ошибка? Почему при первом запросе у меня получается 401, а при втором все работает?
@KaranMer, а почему?




Я хочу предложить лучшее решение.
public class RefreshTokenTransformer<T extends Response<?>> implements ObservableTransformer<T, T> {
private class HttpCode {
private static final int UNAUTHORIZED_HTTP_CODE = 401;
}
private ApiService mApiService;
private UserRepository mUserRepository;
public RefreshTokenTransformer(ApiService service, UserRepository userRepository) {
mApiService = service;
mUserRepository = userRepository;
}
@Override
public ObservableSource<T> apply(final Observable<T> stream) {
return stream.flatMap(new Function<T, ObservableSource<T>>() {
@Override
public ObservableSource<T> apply(T response) throws Exception {
if (response.code() == HttpCode.UNAUTHORIZED_HTTP_CODE) {
return mApiService.refreshToken(mUserRepository.getRefreshTokenHeaders())
.filter(new UnauthorizedPredicate<>(mUserRepository))
.flatMap(new Function<Response<TokenInfo>, ObservableSource<T>>() {
@Override
public ObservableSource<T> apply(Response<TokenInfo> tokenResponse) throws Exception {
return stream.filter(new UnauthorizedPredicate<T>(mUserRepository));
}
});
}
return stream;
}
});
}
private class UnauthorizedPredicate<R extends Response<?>> implements Predicate<R> {
private UserRepository mUserRepository;
private UnauthorizedPredicate(UserRepository userRepository) {
mUserRepository = userRepository;
}
@Override
public boolean test(R response) throws Exception {
if (response.code() == HttpCode.UNAUTHORIZED_HTTP_CODE) {
throw new SessionExpiredException();
}
if (response.body() == null) {
throw new HttpException(response);
}
Class<?> responseBodyClass = response.body().getClass();
if (responseBodyClass.isAssignableFrom(TokenInfo.class)) {
try {
mUserRepository.validateUserAccess((TokenInfo) response.body());
} catch (UnverifiedAccessException error) {
throw new SessionExpiredException(error);
}
}
return true;
}
}
}
Я написал собственный оператор, который выполняет следующие действия:
первый запрос запущен, и мы получаем код ответа 401;
затем выполняем запрос / refresh_token для обновления токена;
после этого, если токен успешно обновился, мы повторяем первый запрос. если токен / refresh_token не работает, мы генерируем исключение
Затем вы можете легко реализовать его в любом таком запросе:
Observable
.compose(new RefreshTokenResponseTransformer<Response<{$your_expected_result}>>
(mApiService, mUserRepository()));
Еще один важный момент: Скорее всего, у вашей начальной наблюдаемой для модернизации есть такие параметры:
mApiService.someRequest(token)
если ожидается, что параметр изменится во время выполнения RefreshTokenTransformer (например, запрос / refresh_token получит новый токен доступа, и вы его где-то сохраните, тогда вы хотите использовать новый токен доступа для повторения запроса), вам нужно будет обернуть наблюдаемое с помощью отложить оператор для принудительного создания нового наблюдаемого, например:
Observable.defer(new Callable<ObservableSource<Response<? extends $your_expected_result>>>() {
@Override
public Response<? extends $your_expected_result> call() throws Exception {
return mApiService.someRequest(token);
}
})
Спасибо за развернутый ответ. Что это за классы: SessionExpiredException, UserRepository, UnverifiedAccessException?
Это мои собственные классы наследования. SessionExpiredException и UnverifiedAccessException - расширяется от исключения для лучшей обработки ошибок. UserRepository - это также мой собственный класс, который инкапсулирует некоторую логику с хранением пользовательской информации. Собственно, это не часть идеи преобразователя обновления токенов, а всего лишь часть моей собственной логики.
Я думаю, что нет необходимости использовать перехватчик, вместо этого вы реализуете Authenticator, с помощью которого вы можете получить доступ к обновленному токену, и okhttp автоматически обработает это. если вы получаете 401, он обновляет заголовок с обновленным токеном и делает новый запрос.
public class TokenAuthenticator implements Authenticator {
@Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
newAccessToken = service.refreshToken();
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header(AUTHORIZATION, newAccessToken)
.build();
}
Вы можете мне больше рассказать?
Причина, по которой вы можете получить 401 при первом запросе, потому что ваш первый запрос должен быть запросом на вход и получить токен, и с этим токеном вы можете сделать свой запрос, и в случае, если срок действия вашего токена истек, будет вызван токен обновления.
если вы используете retrofit2, вам нужно удалить
/из конца вашего базового URL и добавить его перед своим сообщением@POST("/oauth/token")