Aggiornamento del token OAuth mediante Retrofit senza modificare tutte le chiamate


157

Stiamo utilizzando Retrofit nella nostra app Android per comunicare con un server protetto OAuth2. Tutto funziona alla grande, utilizziamo RequestInterceptor per includere il token di accesso ad ogni chiamata. Tuttavia, ci saranno momenti in cui il token di accesso scadrà e il token dovrà essere aggiornato. Alla scadenza del token, la chiamata successiva verrà restituita con un codice HTTP non autorizzato, quindi è facile da monitorare. È possibile modificare ogni chiamata di Retrofit nel modo seguente: Nel callback di errore, verificare il codice di errore, se è uguale a Non autorizzato, aggiornare il token OAuth, quindi ripetere la chiamata di Retrofit. Tuttavia, per questo, tutte le chiamate dovrebbero essere modificate, il che non è facilmente gestibile e una buona soluzione. C'è un modo per farlo senza modificare tutte le chiamate di Retrofit?


1
Questo sembra rilevante per la mia altra domanda . Lo esaminerò di nuovo presto, ma un possibile approccio è il confezionamento di OkHttpClient. Qualcosa del genere: github.com/pakerfeldt/signpost-retrofit Inoltre, poiché sto usando RoboSpice con Retrofit, la creazione di una classe Request di base potrebbe essere un altro possibile approccio. Probabilmente dovrai capire come ottenere il tuo flusso senza un contesto, magari usando Otto / EventBus.
Hassan Ibraheem,

1
Bene, potresti rovesciarlo e rimuovere le custodie non necessarie. Lo esaminerò forse oggi e pubblicherò qui se avessi raggiunto qualcosa che potesse risolvere il nostro problema.
Daniel Zolnai,

2
Si è scoperto che la libreria non gestiva i token rinfrescanti, ma mi ha dato un'idea. Ho fatto un piccolo riassunto su alcuni codici non testati, ma in teoria penso che dovrebbe funzionare: gist.github.com/ZolnaiDani/9710849
Daniel Zolnai,

3
@neworld Una soluzione che mi viene in mente: rendere sincronizzato changeTokenInRequest (...) e, alla prima riga, verificare quando è stata aggiornata l'ultima volta il token. Se è stato solo pochi secondi (millisecondi) fa, non aggiornare il token. È inoltre possibile impostare questo intervallo di tempo a circa 1 ora, per interrompere costantemente la richiesta di nuovi token quando c'è un altro problema esterno al token che non è aggiornato.
Daniel Zolnai,

2
Retrofit 1.9.0 ha appena aggiunto il supporto per OkHttp 2.2, che ha intercettori. Questo dovrebbe rendere il tuo lavoro molto più semplice. Per maggiori informazioni, vedi: github.com/square/retrofit/blob/master/… e github.com/square/okhttp/wiki/Interceptors Devi estendere OkHttp anche per questi, però.
Daniel Zolnai,

Risposte:


214

Si prega di non utilizzare Interceptorsper gestire l'autenticazione.

Attualmente, l'approccio migliore per gestire l'autenticazione è utilizzare la nuova AuthenticatorAPI, progettata appositamente per questo scopo .

OkHttp chiederà automaticamente le Authenticatorcredenziali quando una risposta 401 Not Authorised riprova con loro l' ultima richiesta non riuscita .

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

Attaccalo Authenticatorallo OkHttpClientstesso modo in cui lo faiInterceptors

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

Usa questo client quando crei il tuo Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(ENDPOINT)
                .setClient(new OkClient(okHttpClient))
                .build();
return restAdapter.create(API.class);

6
Questo significa che ogni richiesta fallirà sempre 1 volta o aggiungi il token quando fai le richieste?
Jdruwe,

11
@Jdruwe sembra che questo codice fallirebbe 1 volta, e quindi farà la richiesta. tuttavia se si aggiunge un intercettore il cui unico scopo è quello di aggiungere sempre un token di accesso (indipendentemente dal fatto che sia scaduto o meno), questo verrà chiamato solo quando viene ricevuto un 401 che si verificherà solo quando quel token è scaduto.
narciero,

54
TokenAuthenticatordipende da una serviceclasse. La serviceclasse dipende da OkHttpClientun'istanza. Per creare un OkHttpClientho bisogno di TokenAuthenticator. Come posso interrompere questo ciclo? Due OkHttpClients differenti ? Avranno diversi pool di connessioni ...
Brais Gabin

6
Che ne dici di molte richieste parallele che devono aggiornare il token? Saranno molte richieste di token di aggiornamento contemporaneamente. Come evitarlo?
Igor Kostenko,

10
Ok, quindi la soluzione al problema di @ Ihor potrebbe essere la sincronizzazione del codice all'interno di Authenticator. Ha risolto il problema nel mio caso. nel metodo Richiedi autenticazione (...): - esegui qualsiasi operazione di inizializzazione - avvia il blocco sincronizzato (sincronizzato (MyAuthenticator.class) {...}) - in quel blocco recupera l'accesso corrente e aggiorna token - controlla se la richiesta non riuscita utilizzava l'ultima token di accesso (resp.request (). header ("Autorizzazione")) - se non semplicemente eseguirlo di nuovo con il token di accesso aggiornato - altrimenti esegui il flusso di token di aggiornamento - aggiorna / persiste l'accesso aggiornato e il token di aggiornamento - termina il blocco sincronizzato - esegui nuovamente
Dariusz Wiechecki

65

Se stai usando Retrofit > = 1.9.0allora puoi usare il nuovo Interceptor di OkHttp , che è stato introdotto in OkHttp 2.2.0. Si desidera utilizzare un intercettore di applicazioni , che consente di retry and make multiple calls.

Il tuo Interceptor potrebbe assomigliare a questo pseudocodice:

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

}

Dopo aver definito il tuo Interceptor, crea un OkHttpCliente aggiungi l'interceptor come Application Intercceptor .

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

E infine, usalo OkHttpClientquando crei il tuo RestAdapter.

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

Avvertenza: come Jesse Wilson(da Square) menziona qui , questa è una quantità pericolosa di potenza.

Detto questo, penso sicuramente che questo sia il modo migliore per gestire qualcosa del genere ora. Se avete domande, non esitate a chiedere in un commento.


2
In che modo stai ottenendo una chiamata sincrona in Android quando Android non consente le chiamate di rete sul thread principale? Riscontro problemi durante la restituzione di una risposta da una chiamata asincrona.
lgdroid57,

1
@ lgdroid57 Hai ragione, quindi dovresti già essere su un altro thread quando hai avviato la richiesta originale che ha attivato l'esecuzione dell'interceptor.
theblang,

3
Funzionava alla grande, tranne che dovevo assicurarmi di chiudere la risposta precedente o avrei perso la connessione precedente ... final Request newRequest = request.newBuilder () .... build (); . response.body () close (); return chain.proceed (newRequest);
DallinDyer,

Grazie! Stavo riscontrando un problema in cui il callback della richiesta originale stava ricevendo un messaggio di errore "chiuso" invece della risposta originale, a causa del corpo consumato nell'Interceptor. Sono stato in grado di risolvere questo problema per le risposte riuscite, ma non sono stato in grado di risolverlo per le risposte non riuscite. Eventuali suggerimenti?
lgdroid57,

Grazie @mattblang, sembra carino. Una domanda: la richiesta di richiamata è garantita per essere chiamata anche sul nuovo tentativo?
Luca Fagioli,

23

TokenAuthenticator dipende una classe di servizio. La classe di servizio dipende da un'istanza di OkHttpClient. Per creare un OkHttpClient ho bisogno del TokenAuthenticator. Come posso interrompere questo ciclo? Due diversi client OkHttp? Avranno diversi pool di connessioni ..

Se hai, diciamo, un Retrofit di TokenServicecui hai bisogno all'interno del tuo, Authenticatorma ti piacerebbe crearne uno OkHttpClientper il quale puoi utilizzare TokenServiceHoldercome dipendenza TokenAuthenticator. Dovresti mantenere un riferimento ad esso a livello di applicazione (singleton). Questo è facile se stai usando Dagger 2, altrimenti crea semplicemente un campo di classe all'interno della tua Applicazione.

Nel 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;
    }

In TokenServiceHolder.java:

public class TokenServiceHolder {

    TokenService tokenService = null;

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

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

Configurazione client:

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

Se si utilizza Dagger 2 o un framework di iniezione di dipendenza simile, ci sono alcuni esempi nelle risposte a questa domanda


Dove viene TokenServicecreata la classe?
Yogesh Suthar,

@YogeshSuthar è un servizio di retrofit - vedi la domanda correlata
David Rawson

Grazie, puoi fornire l'implementazione di refreshToken()from service.refreshToken().execute();. Non riesco a trovarne l'implementazione ovunque.
Yogesh Suthar,

@Yogesh Il metodo refreshToken è dalla tua API. Qualunque cosa tu chiami per aggiornare un token (forse una chiamata con nome utente e password?). O forse una richiesta in cui si invia un token e la risposta è un nuovo token
David Rawson,

5

L' TokenAuthenticatoruso della risposta like @theblang è un modo corretto per gestire refresh_token.

Ecco il mio attrezzo (ho usato Kotlin, Dagger, RX ma puoi usare questa idea come attrezzo per il tuo caso)
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()
    }
}

Per prevenire il ciclo di dipendenza come il commento di @Brais Gabin, creo 2 interfacce come

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>
}

e

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

AccessTokenWrapper classe

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 classe

data class AccessToken(
        @Expose
        var token: String,

        @Expose
        var refreshToken: String)

Il mio intercettore

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

Infine, aggiungi Interceptore Authenticatoral tuo OKHttpClientquando crei il servizio PotoAuthApi

dimostrazione

https://github.com/PhanVanLinh/AndroidMVPKotlin

Nota

Flusso di autenticazione
  • Esempio di getImage()codice di ritorno 401 errore API
  • authenticateil metodo all'interno TokenAuthenticatorverrà attivato
  • Sincronizza noneAuthAPI.refreshToken(...)chiamato
  • Dopo la noneAuthAPI.refreshToken(...)risposta -> il nuovo token verrà aggiunto all'intestazione
  • getImage()verrà chiamato AUTO con la nuova intestazione ( HttpLogging NON registrerà questa chiamata) ( interceptall'interno AuthInterceptor NON verrà CHIAMATO )
  • Se il problema getImage()persiste con l'errore 401, il authenticatemetodo interno TokenAuthenticatorverrà nuovamente AGAIN e AGAIN, quindi verrà generato più volte il metodo call ( java.net.ProtocolException: Too many follow-up requests). Puoi prevenirlo contando la risposta . Esempio, se return nullnel authenticatedopo 3 volte riprovare, getImage()sarà finire ereturn response 401

  • Se la getImage()risposta ha esito positivo => si otterrà il risultato normalmente (come si chiama getImage()senza errori)

Spero che sia d'aiuto


Questa soluzione utilizza 2 diversi OkHttpClients, come evidente nella tua classe ServiceGenerator.
SpecialSnowflake

@SpecialSnowflake hai ragione. Se segui la mia soluzione, devi creare 2 OkHttp perché ho creato 2 servizi (oauth e none auth). Penso che non causerà alcun problema. Fammi sapere la tua idea
Phan Van Linh,

1

Conosco questo vecchio thread, ma nel caso qualcuno ci fosse inciampato.

TokenAuthenticator dipende una classe di servizio. La classe di servizio dipende da un'istanza di OkHttpClient. Per creare un OkHttpClient ho bisogno del TokenAuthenticator. Come posso interrompere questo ciclo? Due diversi client OkHttp? Avranno diversi pool di connessioni ..

Stavo affrontando lo stesso problema, ma volevo creare solo un OkHttpClient perché non penso di averne bisogno solo per il TokenAuthenticator stesso, stavo usando Dagger2, quindi ho finito per fornire la classe di servizio come Lazy iniettato nel TokenAuthenticator, puoi leggere di più sull'iniezione Lazy nel pugnale 2 qui , ma è come dire sostanzialmente a Dagger di NON andare subito a creare il servizio necessario per TokenAuthenticator.

È possibile fare riferimento a questo thread SO per il codice di esempio: come risolvere una dipendenza circolare mentre si utilizza ancora Dagger2?


0

Puoi provare a creare una classe base per tutti i tuoi caricatori in cui sarai in grado di catturare una particolare eccezione e quindi agire di cui hai bisogno. Fai estendere tutti i tuoi diversi caricatori dalla classe base per diffondere il comportamento.


Il retrofit non funziona in questo modo. Utilizza le annotazioni Java e le interfacce per descrivere una chiamata API
Daniel Zolnai,

So come funziona il retrofit, ma stai ancora "avvolgendo" le tue chiamate API all'interno di un AsynTask, vero?
k3v1n4ud3

No, utilizzo le chiamate con un callback, quindi vengono eseguite in modo asincrono.
Daniel Zolnai,

Quindi probabilmente puoi creare una classe di callback di base e estenderla a tutti i callback.
k3v1n4ud3

2
Qualche soluzione a questo? È esattamente il mio caso qui. = /
Hugo Nogueira,

0

Dopo lunghe ricerche, ho personalizzato il client Apache per gestire AccessToken di aggiornamento per il retrofit in cui si invia il token di accesso come parametro.

Avvia il tuo adattatore con cookie Client persistente

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

Cookie Client persistente che mantiene i cookie per tutte le richieste e verifica con ogni risposta richiesta, se si tratta di accesso non autorizzato ERROR_CODE = 401, aggiorna il token di accesso e richiama la richiesta, altrimenti elabora solo la richiesta.

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

C'è un motivo per cui stai usando ApacheClient invece della soluzione suggerita? Non che non sia una buona soluzione, ma ha bisogno di molta più codifica, rispetto all'utilizzo di Interceptor.
Daniel Zolnai,

È personalizzato per essere client persistente dei cookie, mantiene la sessione durante i servizi. Anche in Request Intercceptor, puoi aggiungere accesstoken nelle intestazioni. Ma cosa succede se si desidera aggiungerlo come parametro? Anche OKHTTPClient sta avendo dei limiti. rif: stackoverflow.com/questions/24594823/…
Suneel Prakash

È più generalizzato da utilizzare in ogni caso 1. Cookie client persistente 2. Accetta richieste HTTP e HTTPS 3. Aggiorna token di accesso in Params.
Suneel Prakash,

0

Utilizzando un intercettore (iniettare il token) e un autenticatore (operazioni di aggiornamento) eseguono il lavoro ma:

Ho anche avuto un doppio problema di chiamata: la prima chiamata restituiva sempre un 401 : il token non veniva iniettato alla prima chiamata (intercettore) e veniva chiamato l'autenticatore: venivano fatte due richieste.

La correzione era solo per riapplicare la richiesta alla build in Interceptor:

PRIMA:

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

DOPO:

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

IN UN BLOCCO:

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

Spero che sia d'aiuto.

Modifica: non ho trovato un modo per evitare che la prima chiamata restituisse sempre 401 usando solo l'autenticatore e nessun intercettore


-2

A chiunque volesse risolvere chiamate simultanee / parallele durante l'aggiornamento del token. Ecco una soluzione alternativa

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
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.