Обновление токена OAuth с использованием Retrofit без изменения всех вызовов

157

Мы используем Retrofit в нашем Android-приложении для связи с защищенным сервером OAuth2. Все отлично работает, мы используем RequestInterceptor для включения токена доступа при каждом вызове. Однако будут времена, когда срок действия токена доступа истечет, и токен необходимо обновить. Когда срок действия токена истечет, следующий вызов вернется с неавторизованным HTTP-кодом, поэтому его легко отслеживать. Мы могли бы изменить каждый вызов Retrofit следующим образом: в обратном вызове сбоя проверьте код ошибки, если он равен Unauthorized, обновите токен OAuth, затем повторите вызов Retrofit. Однако для этого необходимо изменить все вызовы, что не является легко обслуживаемым и хорошим решением. Есть ли способ сделать это, не изменяя все вызовы Retrofit?

Даниэль Золнай
источник
1
Это выглядит актуально для моего другого вопроса . Скоро я еще вернусь к этому, но один из возможных подходов - это упаковка OkHttpClient. Примерно так: github.com/pakerfeldt/signpost-retrofit Кроме того, поскольку я использую RoboSpice с Retrofit, создание базового класса Request также может быть другим возможным подходом. Возможно, вам придется выяснить, как добиться вашего потока без контекста, хотя, возможно, с помощью Otto / EventBus.
Хасан Ибрагим
1
Ну, вы могли бы раскошелиться, и удалить ненужные случаи. Я посмотрю на это, может быть, сегодня, и напишу здесь, если я достиг чего-то, что может решить нашу проблему.
Даниэль Золнай
2
Оказалось, что библиотека не обрабатывает токены обновления, но дала мне идею. Я сделал небольшую суть по поводу некоторого непроверенного кода, но теоретически я думаю, что он должен работать: gist.github.com/ZolnaiDani/9710849
Даниэль
3
@neworld Решение, которое я могу придумать: синхронизировать changeTokenInRequest (...) и в первой строке проверить, когда последний раз обновлялся токен. Если это было всего несколько секунд (миллисекунд) назад, не обновляйте токен. Вы также можете установить этот таймфрейм на 1 час или около того, чтобы прекратить постоянно запрашивать новые токены, когда есть другая проблема за пределами устаревшего токена.
Даниэль Золнай,
2
Retrofit 1.9.0 только что добавил поддержку OkHttp 2.2, в которой есть перехватчики. Это должно сделать вашу работу намного проще. Для получения дополнительной информации см. Github.com/square/retrofit/blob/master/… и github.com/square/okhttp/wiki/Interceptors. Однако для этих целей вы также должны расширить OkHttp.
Даниэль Золнай

Ответы:

214

Пожалуйста, не используйте Interceptorsдля проверки подлинности.

В настоящее время лучшим подходом к обработке аутентификации является использование нового AuthenticatorAPI, разработанного специально для этой цели .

OkHttp будет автоматически задатьAuthenticator учетные данные , когда ответ 401 Not Authorised Повторная отправка последний запрос не удалось с ними.

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();
    }

    @Override
    public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
        // Null indicates no attempt to authenticate.
        return null;
    }

Прикрепите Authenticatorк так OkHttpClientже, как вы делаете сInterceptors

OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setAuthenticator(authAuthenticator);

Используйте этот клиент при создании вашего Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(ENDPOINT)
                .setClient(new OkClient(okHttpClient))
                .build();
return restAdapter.create(API.class);
lgvalle
источник
6
Означает ли это, что каждый запрос будет терпеть неудачу всегда 1 раз или вы добавляете токен при выполнении запросов?
Jdruwe
11
@Jdruwe похоже, что этот код не будет выполнен 1 раз, а затем он сделает запрос. однако, если вы добавляете перехватчик, единственная цель которого состоит в том, чтобы всегда добавлять токен доступа (независимо от того, истек ли он или нет), тогда он будет вызываться только при получении 401, что произойдет только после истечения срока действия этого токена.
Нарциеро
54
TokenAuthenticatorзависит от serviceкласса. serviceКласс зависит от OkHttpClientэкземпляра. Для создания OkHttpClientмне нужно TokenAuthenticator. Как я могу разорвать этот цикл? Два разных OkHttpClientс? У них будут разные пулы соединений ...
Брейс Габин
6
Как насчет множества параллельных запросов, которым необходимо обновить токен? Будет много запросов на обновление токена одновременно. Как этого избежать?
Игорь Костенко
10
Итак, решение проблемы @ Ihor может заключаться в синхронизации кода внутри Authenticator. Это решило проблему в моем случае. в методе Request authenticate (...): - выполнить любые действия по инициализации - запустить синхронизированный блок (synchronized (MyAuthenticator.class) {...}) - в этом блоке получить текущий токен доступа и обновить маркер - проверить, использовался ли последний запрос с ошибкой токен доступа (resp.request (). header («Авторизация»)) - если не просто запустить его еще раз с обновленным токеном доступа - в противном случае запустить обновление потока токенов - обновить / сохранить обновленный доступ и обновить токен - завершить синхронизированный блок - выполнить повторно
Дариуш Вехецки
65

Если вы используете Retrofit > =, 1.9.0то вы можете использовать новый перехватчик OkHttp , который был представлен в . Вы хотели бы использовать перехватчик приложений , который позволяет вам .OkHttp 2.2.0retry and make multiple calls

Ваш перехватчик может выглядеть примерно так:

public class CustomInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        // try the request
        Response response = chain.proceed(request);

        if (response shows expired token) {

            // get a new token (I use a synchronous Retrofit call)

            // create a new request and modify it accordingly using the new token
            Request newRequest = request.newBuilder()...build();

            // retry the request
            return chain.proceed(newRequest);
        }

        // otherwise just pass the original response on
        return response;
    }

}

После того, как вы определите свой Interceptor, создайте OkHttpClientи добавьте перехватчик в качестве перехватчика приложения .

    OkHttpClient okHttpClient = new OkHttpClient();
    okHttpClient.interceptors().add(new CustomInterceptor());

И, наконец, используйте это OkHttpClientпри создании RestAdapter.

    RestService restService = new RestAdapter().Builder
            ...
            .setClient(new OkClient(okHttpClient))
            .create(RestService.class);

Предупреждение: как Jesse Wilson(с площади) здесь упоминается , это опасное количество энергии.

С учетом сказанного, я определенно думаю, что сейчас это лучший способ справиться с чем-то подобным. Если у вас есть какие-либо вопросы, пожалуйста, не стесняйтесь спрашивать в комментарии.

theblang
источник
2
Как добиться синхронного вызова в Android, когда Android не разрешает сетевые вызовы в главном потоке? У меня проблемы с возвратом ответа от асинхронного вызова.
lgdroid57
1
@ lgdroid57 Вы правы, поэтому вы уже должны быть в другом потоке, когда вы начали исходный запрос, который запустил перехватчик для запуска.
Theblang
3
Это работало отлично, за исключением того, что я должен был убедиться, что предыдущий ответ был закрыт, или я бы потерял предыдущее соединение ... final Request newRequest = request.newBuilder () .... build (); . response.body () близко (); return chain.proceed (newRequest);
DallinDyer
Спасибо! Я столкнулся с проблемой, когда обратный вызов исходного запроса получал сообщение об ошибке «закрыто» вместо исходного ответа из-за того, что тело было перехвачено в перехватчике. Мне удалось исправить это для успешных ответов, но я не смог исправить это для неудачных ответов. Какие-либо предложения?
lgdroid57
Спасибо @mattblang, это выглядит красиво. Один вопрос: гарантированно вызывается обратный вызов запроса даже при повторной попытке?
Лука Фаджиоли
23

TokenAuthenticator зависит от класса обслуживания. Класс обслуживания зависит от экземпляра OkHttpClient. Для создания OkHttpClient мне нужен TokenAuthenticator. Как я могу разорвать этот цикл? Два разных клиента OkHttp? У них будут разные пулы соединений.

Если у вас есть, скажем, Retrofit, TokenServiceкоторый вам нужен, Authenticatorно вы бы хотели установить только тот, для которого OkHttpClientвы можете использовать TokenServiceHolderкак зависимость TokenAuthenticator. Вы должны будете поддерживать ссылку на него на уровне приложения (singleton). Это легко, если вы используете Dagger 2, иначе просто создайте поле класса внутри вашего Приложения.

В TokenAuthenticator.java

public class TokenAuthenticator implements Authenticator {

    private final TokenServiceHolder tokenServiceHolder;

    public TokenAuthenticator(TokenServiceHolder tokenServiceHolder) {
        this.tokenServiceHolder = tokenServiceHolder;
    }

    @Override
    public Request authenticate(Proxy proxy, Response response) throws IOException {

        //is there a TokenService?
        TokenService service = tokenServiceHolder.get();
        if (service == null) {
            //there is no way to answer the challenge
            //so return null according to Retrofit's convention
            return null;
        }

        // Refresh your access_token using a synchronous api request
        newAccessToken = service.refreshToken().execute();

        // Add new header to rejected request and retry it
        return response.request().newBuilder()
                .header(AUTHORIZATION, newAccessToken)
                .build();
    }

    @Override
    public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
        // Null indicates no attempt to authenticate.
        return null;
    }

В TokenServiceHolder.java:

public class TokenServiceHolder {

    TokenService tokenService = null;

    @Nullable
    public TokenService get() {
        return tokenService;
    }

    public void set(TokenService tokenService) {
        this.tokenService = tokenService;
    }
}

Настройка клиента:

//obtain instance of TokenServiceHolder from application or singleton-scoped component, then
TokenAuthenticator authenticator = new TokenAuthenticator(tokenServiceHolder);
OkHttpClient okHttpClient = new OkHttpClient();    
okHttpClient.setAuthenticator(tokenAuthenticator);

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .client(okHttpClient)
    .build();

TokenService tokenService = retrofit.create(TokenService.class);
tokenServiceHolder.set(tokenService);

Если вы используете Dagger 2 или аналогичную инфраструктуру внедрения зависимостей, в ответах на этот вопрос есть несколько примеров.

Дэвид Роусон
источник
Где TokenServiceсоздается класс?
Йогеш Сутар
@YogeshSuthar это сервис по модернизации - см. Соответствующий вопрос
Дэвид Роусон,
Спасибо, можете ли вы предоставить реализацию refreshToken()от service.refreshToken().execute();. Не в состоянии найти его реализацию нигде.
Йогеш Сутар
@Yogesh Метод refreshToken из вашего API. Что бы вы ни звонили, чтобы обновить токен (возможно, звонок с именем пользователя и паролем?). Или, может быть, запрос, где вы отправляете токен, а ответом является новый токен
Дэвид Роусон
5

Использование TokenAuthenticatorответа типа @theblang является правильным способом обработки refresh_token.

Вот мой инструмент (я использую Kotlin, Dagger, RX, но вы можете использовать эту идею для реализации в вашем случае)
TokenAuthenticator

class TokenAuthenticator @Inject constructor(private val noneAuthAPI: PotoNoneAuthApi, private val accessTokenWrapper: AccessTokenWrapper) : Authenticator {

    override fun authenticate(route: Route, response: Response): Request? {
        val newAccessToken = noneAuthAPI.refreshToken(accessTokenWrapper.getAccessToken()!!.refreshToken).blockingGet()
        accessTokenWrapper.saveAccessToken(newAccessToken) // save new access_token for next called
        return response.request().newBuilder()
                .header("Authorization", newAccessToken.token) // just only need to override "Authorization" header, don't need to override all header since this new request is create base on old request
                .build()
    }
}

Для предотвращения цикла зависимостей типа комментария @Brais Gabin, я создаю 2 интерфейса:

interface PotoNoneAuthApi { // NONE authentication API
    @POST("/login")
    fun login(@Body request: LoginRequest): Single<AccessToken>

    @POST("refresh_token")
    @FormUrlEncoded
    fun refreshToken(@Field("refresh_token") refreshToken: String): Single<AccessToken>
}

и

interface PotoAuthApi { // Authentication API
    @GET("api/images")
    fun getImage(): Single<GetImageResponse>
}

AccessTokenWrapper класс

class AccessTokenWrapper constructor(private val sharedPrefApi: SharedPrefApi) {
    private var accessToken: AccessToken? = null

    // get accessToken from cache or from SharePreference
    fun getAccessToken(): AccessToken? {
        if (accessToken == null) {
            accessToken = sharedPrefApi.getObject(SharedPrefApi.ACCESS_TOKEN, AccessToken::class.java)
        }
        return accessToken
    }

    // save accessToken to SharePreference
    fun saveAccessToken(accessToken: AccessToken) {
        this.accessToken = accessToken
        sharedPrefApi.putObject(SharedPrefApi.ACCESS_TOKEN, accessToken)
    }
}

AccessToken класс

data class AccessToken(
        @Expose
        var token: String,

        @Expose
        var refreshToken: String)

Мой перехватчик

class AuthInterceptor @Inject constructor(private val accessTokenWrapper: AccessTokenWrapper): Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val authorisedRequestBuilder = originalRequest.newBuilder()
                .addHeader("Authorization", accessTokenWrapper.getAccessToken()!!.token)
                .header("Accept", "application/json")
        return chain.proceed(authorisedRequestBuilder.build())
    }
}

Наконец, добавьте Interceptorи Authenticatorк вашему OKHttpClientпри создании службы PotoAuthApi

демонстрация

https://github.com/PhanVanLinh/AndroidMVPKotlin

Заметка

Аутентификатор потока
  • Пример getImage()возврата API 401 код ошибки
  • authenticateметод внутри TokenAuthenticatorбудет уволен
  • Синхронизировать noneAuthAPI.refreshToken(...)называется
  • После noneAuthAPI.refreshToken(...)ответа -> новый токен добавит в шапку
  • getImage()будет вызываться AUTO с новым заголовком ( HttpLogging НЕ будет регистрировать этот вызов) ( interceptвнутри не AuthInterceptor будет вызываться )
  • Если getImage()ошибка 401 все еще не сработала, authenticateметод внутри TokenAuthenticatorбудет вызывать ОПЯТЬ и СНОВА, а затем будет много раз выдавать ошибку о вызове метода ( java.net.ProtocolException: Too many follow-up requests). Вы можете предотвратить это, посчитав ответ . Например, если вы return nullв authenticateпосле 3 -х раз повторить, getImage()будет закончить иreturn response 401

  • Если getImage()ответ успешен =>, мы приведем результат в обычном режиме (как вы звоните getImage()без ошибок)

Надеюсь, это поможет

Фан Ван Линь
источник
Это решение использует 2 разных OkHttpClients, как видно из вашего класса ServiceGenerator.
SpecialSnowflake
@ SpecialSnowflake ты прав. Если вы следуете моему решению, вам нужно создать 2 OkHttp, потому что я создал 2 сервиса (oauth и none auth). Я думаю, что это не вызовет никаких проблем. Дайте мне знать вашу идею
Фан Ван Лин
1

Я знаю эту старую ветку, но на всякий случай кто-то наткнулся на нее.

TokenAuthenticator зависит от класса обслуживания. Класс обслуживания зависит от экземпляра OkHttpClient. Для создания OkHttpClient мне нужен TokenAuthenticator. Как я могу разорвать этот цикл? Два разных клиента OkHttp? У них будут разные пулы соединений.

Я столкнулся с той же проблемой, но я хотел создать только один OkHttpClient, потому что я не думаю, что мне нужен другой только для самого TokenAuthenticator, я использовал Dagger2, поэтому я в итоге предоставил класс обслуживания, как Lazy внедрен в TokenAuthenticator, вы можете прочитать больше о Lazy инъекции в dagger 2 здесь , но это все равно что сказать Dagger НЕ идти и создавать сервис, необходимый для TokenAuthenticator сразу.

Вы можете обратиться к этой теме SO за примером кода: Как разрешить циклическую зависимость, все еще используя Dagger2?

Бода
источник
0

Вы можете попробовать создать базовый класс для всех ваших загрузчиков, в котором вы сможете перехватить конкретное исключение и затем действовать так, как вам нужно. Сделайте так, чтобы все ваши различные загрузчики расширялись от базового класса, чтобы распространить поведение.

k3v1n4ud3
источник
Дооснащение не работает таким образом. Он использует Java-аннотации и интерфейсы для описания вызова API
Daniel Zolnai
Я знаю, как работает модернизация, но вы все еще «оборачиваете» свои вызовы API внутри AsynTask, не так ли?
k3v1n4ud3
Нет, я использую вызовы с обратным вызовом, поэтому они выполняются асинхронно.
Даниэль Золнай,
Тогда вы, вероятно, можете создать базовый класс обратного вызова и сделать так, чтобы все ваши обратные вызовы расширяли его.
k3v1n4ud3
2
Любое решение этого? Это точно мой случай здесь. = /
Уго Ногейра
0

После долгих исследований я настроил клиент Apache для обработки обновления AccessToken For Retrofit, в котором вы отправляете маркер доступа в качестве параметра.

Инициируйте свой адаптер с постоянным клиентом cookie

restAdapter = new RestAdapter.Builder()
                .setEndpoint(SERVER_END_POINT)
                .setClient(new CookiePersistingClient())
                .setLogLevel(RestAdapter.LogLevel.FULL).build();

Cookie Постоянный клиент, который поддерживает куки для всех запросов и проверяет каждый ответ на запрос, если это неавторизованный доступ ERROR_CODE = 401, обновляет токен доступа и отзывает запрос, иначе просто обрабатывает запрос.

private static class CookiePersistingClient extends ApacheClient {

    private static final int HTTPS_PORT = 443;
    private static final int SOCKET_TIMEOUT = 300000;
    private static final int CONNECTION_TIMEOUT = 300000;

    public CookiePersistingClient() {
        super(createDefaultClient());
    }

    private static HttpClient createDefaultClient() {
        // Registering https clients.
        SSLSocketFactory sf = null;
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore
                    .getDefaultType());
            trustStore.load(null, null);

            sf = new MySSLSocketFactory(trustStore);
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        HttpParams params = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(params,
                CONNECTION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("https", sf, HTTPS_PORT));
        // More customization (https / timeouts etc) can go here...

        ClientConnectionManager cm = new ThreadSafeClientConnManager(
                params, registry);
        DefaultHttpClient client = new DefaultHttpClient(cm, params);

        // Set the default cookie store
        client.setCookieStore(COOKIE_STORE);

        return client;
    }

    @Override
    protected HttpResponse execute(final HttpClient client,
            final HttpUriRequest request) throws IOException {
        // Set the http context's cookie storage
        BasicHttpContext mHttpContext = new BasicHttpContext();
        mHttpContext.setAttribute(ClientContext.COOKIE_STORE, COOKIE_STORE);
        return client.execute(request, mHttpContext);
    }

    @Override
    public Response execute(final Request request) throws IOException {
        Response response = super.execute(request);
        if (response.getStatus() == 401) {

            // Retrofit Callback to handle AccessToken
            Callback<AccessTockenResponse> accessTokenCallback = new Callback<AccessTockenResponse>() {

                @SuppressWarnings("deprecation")
                @Override
                public void success(
                        AccessTockenResponse loginEntityResponse,
                        Response response) {
                    try {
                        String accessToken =  loginEntityResponse
                                .getAccessToken();
                        TypedOutput body = request.getBody();
                        ByteArrayOutputStream byte1 = new ByteArrayOutputStream();
                        body.writeTo(byte1);
                        String s = byte1.toString();
                        FormUrlEncodedTypedOutput output = new FormUrlEncodedTypedOutput();
                        String[] pairs = s.split("&");
                        for (String pair : pairs) {
                            int idx = pair.indexOf("=");
                            if (URLDecoder.decode(pair.substring(0, idx))
                                    .equals("access_token")) {
                                output.addField("access_token",
                                        accessToken);
                            } else {
                                output.addField(URLDecoder.decode(
                                        pair.substring(0, idx), "UTF-8"),
                                        URLDecoder.decode(
                                                pair.substring(idx + 1),
                                                "UTF-8"));
                            }
                        }
                        execute(new Request(request.getMethod(),
                                request.getUrl(), request.getHeaders(),
                                output));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }

                @Override
                public void failure(RetrofitError error) {
                    // Handle Error while refreshing access_token
                }
            };
            // Call Your retrofit method to refresh ACCESS_TOKEN
            refreshAccessToken(GRANT_REFRESH,CLIENT_ID, CLIENT_SECRET_KEY,accessToken, accessTokenCallback);
        }

        return response;
    }
}
Сунель Пракаш
источник
Есть ли причина, по которой вы используете ApacheClient вместо предложенного решения? Не то чтобы это не было хорошим решением, но оно требует гораздо большего кодирования по сравнению с использованием перехватчиков.
Даниэль Золнай
Он настроен, чтобы быть постоянным клиентом cookie, поддерживает сессию во всех сервисах. Даже в Request Intercceptor вы можете добавить accesstoken в заголовки. Но что, если вы хотите добавить его в качестве параметра? Также у OKHTTPClient есть ограничения. ссылка: stackoverflow.com/questions/24594823/…
Suneel Prakash
Оно более обобщенно для использования в любом случае 1. Постоянный клиент cookie 2. Принимает запросы HTTP и HTTPS 3. Обновление токена доступа в параметрах.
Suneel Prakash
0

Использование одного перехватчика (инъекция токена) и одного аутентификатора (операции обновления) делают эту работу, но:

У меня также была проблема двойного вызова: первый вызов всегда возвращал 401 : токен не был введен при первом вызове (перехватчик), и был вызван аутентификатор: было сделано два запроса.

Исправление состояло в том, чтобы просто отразить запрос на сборку в Interceptor:

ПЕРЕД:

private Interceptor getInterceptor() {
    return (chain) -> {
        Request request = chain.request();
        //...
        request.newBuilder()
                .header(AUTHORIZATION, token))
                .build();
        return chain.proceed(request);
    };
}

ПОСЛЕ:

private Interceptor getInterceptor() {
    return (chain) -> {
        Request request = chain.request();
        //...
        request = request.newBuilder()
                .header(AUTHORIZATION, token))
                .build();
        return chain.proceed(request);
    };
}

В ОДНОМ БЛОКЕ:

private Interceptor getInterceptor() {
    return (chain) -> {
        Request request = chain.request().newBuilder()
                .header(AUTHORIZATION, token))
                .build();
        return chain.proceed(request);
    };
}

Надеюсь, поможет.

Изменить: я не нашел способ избежать первого вызова всегда возвращать 401, используя только аутентификатор и без перехватчика

Sigrun
источник
-2

Всем, кто хотел бы разрешить параллельные / параллельные вызовы при обновлении токена. Вот обходной путь

class TokenAuthenticator: Authenticator {

    override fun authenticate(route: Route?, response: Response?): Request? {
        response?.let {
            if (response.code() == 401) {
                while (true) {
                    if (!isRefreshing) {
                        val requestToken = response.request().header(AuthorisationInterceptor.AUTHORISATION)
                        val currentToken = OkHttpUtil.headerBuilder(UserService.instance.token)

                        currentToken?.let {
                            if (requestToken != currentToken) {
                                return generateRequest(response, currentToken)
                            }
                        }

                        val token = refreshToken()
                        token?.let {
                            return generateRequest(response, token)
                        }
                    }
                }
            }
        }

        return null
    }

    private fun generateRequest(response: Response, token: String): Request? {
        return response.request().newBuilder()
                .header(AuthorisationInterceptor.USER_AGENT, OkHttpUtil.UA)
                .header(AuthorisationInterceptor.AUTHORISATION, token)
                .build()
    }

    private fun refreshToken(): String? {
        synchronized(TokenAuthenticator::class.java) {
            UserService.instance.token?.let {
                isRefreshing = true

                val call = ApiHelper.refreshToken()
                val token = call.execute().body()
                UserService.instance.setToken(token, false)

                isRefreshing = false

                return OkHttpUtil.headerBuilder(token)
            }
        }

        return null
    }

    companion object {
        var isRefreshing = false
    }
}
Андерс Чеу
источник