Оновлення маркера OAuth за допомогою Retrofit без зміни всіх дзвінків


157

Ми використовуємо Retrofit у нашому додатку Android для спілкування із захищеним сервером OAuth2. Все чудово працює, ми використовуємо RequestIntercepttor для включення маркера доступу до кожного дзвінка. Однак наступить час, коли маркер доступу закінчиться, і маркер потрібно оновити. Коли маркер закінчиться, наступний дзвінок повернеться з несанкціонованим HTTP-кодом, тому це легко контролювати. Ми можемо змінити кожен виклик Retrofit наступним чином: У випадку зворотного виклику помилки перевірте код помилки, якщо він дорівнює Несанкціонованому, оновіть маркер OAuth та повторіть виклик модернізації. Однак для цього всі виклики повинні бути модифіковані, що не є легко ремонтом і хорошим рішенням. Чи є спосіб це зробити, не змінюючи всі дзвінки Retrofit?


1
Це схоже на моє інше питання . Я незабаром ще раз перегляну його, але один можливий підхід - завершення роботи OkHttpClient. Приблизно так: github.com/pakerfeldt/signpost-retrofit Крім того, оскільки я використовую RoboSpice з Retrofit, створення базового класу Request також може бути іншим можливим підходом. Можливо, вам доведеться розібратися, як досягти потоку без контексту, можливо, використовуючи Otto / EventBus.
Хассан Ібрагіем

1
Добре, ви могли б розщедритися і вийняти непотрібні корпуси. Я розгляну це, можливо, сьогодні, і опублікую тут, якщо я досяг щось, що могло б вирішити нашу проблему.
Даніель Золнай

2
Виявилося, що бібліотека не обробляє освіжаючі маркери, але дала мені уявлення. Я зробив невеличку суть про якийсь неперевірений код, але теоретично, я думаю, це має спрацювати: gist.github.com/ZolnaiDani/9710849
Daniel Zolnai

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.
Даніель Золнай

Відповіді:


213

Будь ласка, не використовуйте 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);

6
Чи означає це, що кожен запит буде відмовлено завжди 1 раз або ви додаєте маркер під час виконання запитів?
Jdruwe

11
@Jdruwe Схоже, що цей код вийде з ладу 1 раз, і тоді він зробить запит. однак, якщо ви додаєте перехоплювач, яка є єдиною метою - завжди додавати маркер доступу (незалежно від того, термін його дії закінчився чи ні), він буде викликатись лише тоді, коли буде отримано 401, який відбуватиметься лише тоді, коли цей маркер закінчився.
narciero

54
TokenAuthenticatorзалежить serviceклас. serviceКлас залежить від OkHttpClientекземпляра. Щоб створити OkHttpClientI, мені потрібно TokenAuthenticator. Як я можу порушити цей цикл? Два різних OkHttpClients? Вони матимуть різні пули підключення ...
Brais Gabin

6
Як щодо багатьох паралельних запитів, які потрібно оновити маркер? Це буде багато запитів оновлення токенів одночасно. Як цього уникнути?
Ігор Костенко

10
Гаразд, таким чином рішенням проблеми @ Ігора може бути синхронізація коду всередині Authenticator. Це вирішило проблему в моєму випадку. у методі Request authenticate (...): - виконайте будь-які речі з ініталізацією - запустіть блок синхронізованого (синхронізований (MyAuthenticator.class) {...}) - у цьому блоці виберіть поточний маркер доступу та оновлення - перевірте, чи не вдалося запит використовувати останній маркер доступу (resp.request (). заголовок ("Авторизація")) - якщо не просто запустити його ще раз оновленим маркером доступу - інакше запустіть оновити потік маркера - оновіть / збережіть оновлений доступ і оновіть маркер - закінчіть синхронізований блок - повтор
Даріуш Вічецький

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

Попередження: As Jesse Wilson(від площі) згадується тут , це небезпечне кількість енергії.

Зважаючи на це, я, безумовно, думаю, що це найкращий спосіб впоратися з таким чимось зараз. Якщо у вас є якісь питання, будь ласка, не соромтесь запитати їх у коментарі.


2
Як ви досягаєте синхронного дзвінка в Android, коли Android не дозволяє мережеві дзвінки в основний потік? У мене виникають проблеми з поверненням Відповіді від асинхронного дзвінка.
lgdroid57

1
@ lgdroid57 Ви маєте рацію, тому ви вже повинні бути на іншій нитці, коли ви запустили оригінальний запит, який ініціював запуск перехоплювача.
theblang

3
Це спрацювало чудово, за винятком того, що я повинен був переконатись, що закрити попередню відповідь, або я би витік попереднього з'єднання ... остаточний запит newRequest = request.newBuilder () .... build (); response.body (). close (); повернути ланцюг.продовжити (newRequest);
DallinDyer

Дякую! Я зіткнувся з проблемою, коли зворотний виклик вихідного запиту отримував повідомлення про помилку "закрито" замість оригінальної відповіді через те, що тіло споживається в перехоплювачі. Мені вдалося виправити це для успішних відповідей, але я не зміг виправити це для невдалої відповіді. Будь-які пропозиції?
lgdroid57

Дякую @mattblang, це виглядає приємно. Одне запитання: чи гарантовано зворотний виклик дзвінка навіть при повторному спробі?
Лука Фагіолі

23

TokenAuthenticator залежить від класу обслуговування. Клас обслуговування залежить від екземпляра OkHttpClient. Для створення OkHttpClient мені потрібен TokenAuthenticator. Як я можу порушити цей цикл? Два різні OkHttpClients? Вони матимуть різні пули підключення ..

Якщо у вас є, скажімо, модернізація, TokenServiceяка вам потрібна всередині вашого, Authenticatorале ви хотіли б створити лише одну, OkHttpClientяку ви можете використовувати TokenServiceHolderяк залежність TokenAuthenticator. Вам слід підтримувати посилання на нього на рівні заявки (одиночний). Це легко, якщо ви використовуєте 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

Примітка

Аутентифікатор потоку
  • Приклад API getImage()повернення 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. Як я можу порушити цей цикл? Два різні OkHttpClients? Вони матимуть різні пули підключення ..

Я зіткнувся з тією ж проблемою, але я хотів створити тільки одну OkHttpClient тому що я не думаю , що мені потрібно ще один для всього самого TokenAuthenticator, я використовував Dagger2, так що я в кінцевому підсумку забезпечуючи клас обслуговування , як Лінивий впорскується в TokenAuthenticator, ви можете прочитати докладніше про «Ледачу ін'єкцію» в кинджалі 2 тут , але це як би сказати Дагеру НЕ йти і створити послугу, необхідну TokenAuthenticator відразу.

Ви можете звернутися до цього потоку SO для зразкового коду: Як вирішити кругову залежність, використовуючи Dagger2?


0

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


Модернізація не працює так. Він використовує анотації Java та інтерфейси для опису виклику API
Daniel Zolnai

Я знаю, як працює модернізація, але ви все ще "загортаєте" свої дзвінки API всередині AsynTask, чи не так?
k3v1n4ud3

Ні, я використовую дзвінки із зворотним дзвоном, тому вони працюють асинхронно.
Даніель Золнай

Тоді ви, ймовірно, можете створити базовий клас зворотних викликів і змусити всі зворотні дзвінки розширити його.
k3v1n4ud3

2
Будь-яке рішення цього? Тут якраз моя справа. = /
Уго Ногейра

0

Після довгих досліджень я налаштував клієнт Apache для обробки Refreshing AccessToken For Retrofit, в якому ви надсилаєте маркер доступу як параметр.

Ініціюйте свій адаптер за допомогою постійного клієнта

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 ви можете додати запит до заголовків. Але що робити, якщо ви хочете додати його як парам? Також у OKHTTPClient є обмеження. посилання: stackoverflow.com/questions/24594823 / ...
Suneel Пракаш

Він більш узагальнений для використання в будь-якому випадку 1. Постійний клієнт-клієнт 2. Приймає запити HTTP та HTTPS 3. Оновіть маркер доступу в параметрах.
Suneel Prakash

0

За допомогою одного перехоплювача (введіть маркер) та одного аутентифікатора (операції оновлення) виконайте цю роботу, але:

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

Виправлення полягало лише в тому, щоб вплинути на запит на збірку в перехоплювачі:

ПЕРЕД:

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, використовуючи лише автентифікатор і не перехоплювач


-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
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.