Atualizando o token OAuth usando Retrofit sem modificar todas as chamadas

157

Estamos usando o Retrofit em nosso aplicativo Android para se comunicar com um servidor seguro OAuth2. Tudo funciona muito bem, usamos o RequestInterceptor para incluir o token de acesso em cada chamada. No entanto, haverá momentos em que o token de acesso expirará e o token precisará ser atualizado. Quando o token expirar, a próxima chamada retornará com um código HTTP não autorizado, facilitando o monitoramento. Podemos modificar cada chamada de Retrofit da seguinte maneira: No retorno de chamada com falha, verifique o código de erro, se for igual a Não autorizado, atualize o token OAuth e repita a chamada de Retrofit. No entanto, para isso, todas as chamadas devem ser modificadas, o que não é uma solução de fácil manutenção e boa. Existe uma maneira de fazer isso sem modificar todas as chamadas de retrofit?

Daniel Zolnai
fonte
1
Isso parece relevante para minha outra pergunta . Analisarei novamente em breve, mas uma abordagem possível é agrupar o OkHttpClient. Algo assim: github.com/pakerfeldt/signpost-retrofit Além disso, como estou usando o RoboSpice com o Retrofit, a criação de uma classe Request básica também pode ser outra abordagem possível. Provavelmente, você precisará descobrir como atingir seu fluxo sem um Contexto, talvez usando o Otto / EventBus.
Hassan Ibraheem 21/03
1
Bem, você pode bifurcar e remover os casos desnecessários. Talvez eu deva investigar isso hoje e postar aqui se consegui algo que pudesse resolver nosso problema.
Daniel Zolnai 22/03
2
Acabou que a biblioteca não lidava com tokens atualizados, mas me deu uma ideia. Eu fiz uma pequena essência sobre algum código não testado, mas, em teoria, eu acho que deve funcionar: gist.github.com/ZolnaiDani/9710849
Daniel Zolnai
3
@neworld Uma solução em que consigo pensar: sincronize o changeTokenInRequest (...) e, na primeira linha, verifique quando foi a última vez que o token foi atualizado. Se já faz alguns segundos (milissegundos) atrás, não atualize o token. Você também pode definir esse período para 1 hora ou mais, para parar de solicitar constantemente novos tokens quando houver outro problema fora do token que está desatualizado.
Daniel Zolnai
2
O Retrofit 1.9.0 acabou de adicionar suporte ao OkHttp 2.2, que possui interceptores. Isso deve facilitar muito o seu trabalho. Para obter mais informações, consulte: github.com/square/retrofit/blob/master/… e github.com/square/okhttp/wiki/Interceptors. Você também deve estender o OkHttp para estes.
Daniel Zolnai

Respostas:

213

Por favor, não use Interceptorspara lidar com autenticação.

Atualmente, a melhor abordagem para lidar com a autenticação é usar a nova AuthenticatorAPI, projetada especificamente para essa finalidade .

O OkHttp solicitará automaticamente as Authenticatorcredenciais quando uma resposta estiver 401 Not Authorised tentando novamente a última solicitação com falha .

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

Anexar um Authenticatora um OkHttpClientda mesma forma que você faz comInterceptors

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

Use este cliente ao criar seu Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(ENDPOINT)
                .setClient(new OkClient(okHttpClient))
                .build();
return restAdapter.create(API.class);
lgvalle
fonte
6
Isso significa que toda solicitação falhará sempre uma vez ou você adiciona o token ao fazer solicitações?
precisa saber é o seguinte
11
@Jdruwe parece que esse código falharia 1 vez e, em seguida, fará a solicitação. no entanto, se você adicionar um interceptador, o único objetivo é sempre adicionar um token de acesso (independentemente de ter expirado ou não), e isso só será chamado quando um 401 for recebido, o que ocorrerá somente quando esse token expirar.
Narciero 3/10
54
TokenAuthenticatordepende de uma serviceclasse. A serviceclasse depende de uma OkHttpClientinstância. Para criar um OkHttpClienteu preciso do TokenAuthenticator. Como posso quebrar esse ciclo? Dois OkHttpClients diferentes ? Eles vão ter diferentes pools de conexão ...
Brais Gabin
6
Que tal muitas solicitações paralelas que precisam atualizar o token? Serão muitas solicitações de token de atualização ao mesmo tempo. Como evitá-lo?
Igor Kostenko
10
Ok, então a solução para o problema de @ Ihor pode estar sincronizando o código dentro do Authenticator. Resolveu o problema no meu caso. no método Solicitar autenticação (...): - faça qualquer coisa de inicialização - inicie o bloco sincronizado (sincronizado (MyAuthenticator.class) {...}) - nesse bloco, recupere o acesso atual e atualize o token - verifique se a solicitação com falha está usando a última versão token de acesso (resp.request () header ( "Autorização").) - se não apenas executá-lo novamente com acesso atualizados símbolo - atualização de qualquer forma executar token de fluxo - / persistem atualizados acesso atualização e refrescar símbolo - acabamento sincronizado bloco - reprise
Dariusz Wiechecki
65

Se você estiver usando o Retrofit > = 1.9.0, poderá usar o novo Interceptor do OkHttp , que foi introduzido no OkHttp 2.2.0. Você gostaria de usar um Application Interceptor , que permite retry and make multiple calls.

Seu Interceptor pode se parecer com este pseudocódigo:

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

}

Depois de definir o seu Interceptor, crie um OkHttpCliente adicione o interceptor como um Application Interceptor .

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

E, finalmente, use isso OkHttpClientao criar o seu RestAdapter.

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

Aviso: Como Jesse Wilson(da Square) menciona aqui , essa é uma quantidade perigosa de energia.

Com isso dito, eu definitivamente acho que essa é a melhor maneira de lidar com algo assim agora. Se você tiver alguma dúvida, não hesite em perguntar em um comentário.

theblang
fonte
2
Como você está conseguindo uma chamada síncrona no Android quando o Android não permite chamadas de rede no segmento principal? Estou com problemas para retornar uma resposta de uma chamada assíncrona.
lgdroid57
1
@ lgdroid57 Você está correto, portanto, você já deveria estar em outro encadeamento quando iniciou a solicitação original que acionou a execução do interceptador.
theblang
3
Isso funcionou muito bem, exceto que eu precisava fechar a resposta anterior ou vazaria a conexão anterior ... final Request newRequest = request.newBuilder () .... build (); response.body (). close (); return chain.proceed (newRequest);
DallinDyer
Obrigado! Eu estava com um problema em que o retorno de chamada da solicitação original estava recebendo uma mensagem de erro "fechado" em vez da resposta original, devido ao corpo ser consumido no Interceptador. Consegui corrigir isso para respostas bem-sucedidas, mas não consegui corrigir isso para respostas com falha. Alguma sugestão?
lgdroid57
Obrigado @mattblang, parece bom. Uma pergunta: é garantido que o retorno de chamada da solicitação seja chamado mesmo na nova tentativa?
Luca Fagioli
23

TokenAuthenticator depende de uma classe de serviço. A classe de serviço depende de uma instância OkHttpClient. Para criar um OkHttpClient, preciso do TokenAuthenticator. Como posso quebrar esse ciclo? Dois OkHttpClients diferentes? Eles terão diferentes pools de conexão ..

Se você tem, por exemplo, um Retrofit de TokenServiceque precisa dentro do seu, Authenticatormas só deseja configurar um para o OkHttpClientqual pode usar um TokenServiceHoldercomo uma dependência TokenAuthenticator. Você precisaria manter uma referência a ele no nível do aplicativo (singleton). Isso é fácil se você estiver usando o Dagger 2, caso contrário, apenas crie um campo de classe dentro do seu Aplicativo.

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

Em TokenServiceHolder.java:

public class TokenServiceHolder {

    TokenService tokenService = null;

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

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

Configuração do cliente:

//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 você estiver usando o Dagger 2 ou uma estrutura semelhante de injeção de dependência, existem alguns exemplos nas respostas a esta pergunta

David Rawson
fonte
Onde a TokenServiceclasse é criada?
Yogesh Suthar
@YogeshSuthar é um serviço Retrofit - veja a questão conexa
David Rawson
Obrigado, você pode fornecer a implementação de refreshToken()from service.refreshToken().execute();. Não é possível encontrar sua implementação em qualquer lugar.
Yogesh Suthar
@Yogesh O método refreshToken é da sua API. Tudo o que você chama para atualizar um token (uma chamada com nome de usuário e senha, talvez?). Ou talvez uma solicitação em que você envie um token e a resposta seja um novo token
David Rawson
5

Usar TokenAuthenticatoruma resposta como @theblang é uma maneira correta de lidar refresh_token.

Aqui está o meu implemento (usei Kotlin, Dagger, RX, mas você pode usar essa ideia para implantar no seu 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()
    }
}

Para evitar o ciclo de dependência, como o comentário de @Brais Gabin, eu crio 2 interfaces como

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)

My Interceptor

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

Por fim, adicione Interceptore Authenticatorao seu OKHttpClientserviço de criação PotoAuthApi

Demo

https://github.com/PhanVanLinh/AndroidMVPKotlin

Nota

Fluxo do autenticador
  • getImage()Código de erro de retorno 401 da API de exemplo
  • authenticatemétodo dentro TokenAuthenticatorserá acionado
  • Sincronizar noneAuthAPI.refreshToken(...)chamado
  • Após a noneAuthAPI.refreshToken(...)resposta -> novo token será adicionado ao cabeçalho
  • getImage()será chamado automaticamente com o novo cabeçalho ( HttpLogging NÃO registrará esta chamada) ( interceptdentro AuthInterceptor NÃO SERÁ CHAMADO )
  • Se getImage()ainda falhar com o erro 401, o authenticatemétodo interno TokenAuthenticatorserá acionado novamente e novamente, em seguida, gerará erro sobre o método de chamada muitas vezes ( java.net.ProtocolException: Too many follow-up requests). Você pode evitá-lo pela resposta da contagem . Exemplo, se você return nullem authenticateapós 3 vezes repetir, getImage()vai terminar ereturn response 401

  • Se a getImage()resposta tiver êxito =>, o resultado será o resultado normalmente (como você chama getImage()sem erro)

Espero que ajude

Phan Van Linh
fonte
Esta solução usa 2 OkHttpClients diferentes, como é evidente na sua classe ServiceGenerator.
SpecialSnowflake
@SpecialSnowflake você está certo. Se você seguir minha solução, precisará criar 2 OkHttp porque criei 2 serviços (oauth e none auth). Eu acho que não causará nenhum problema. Deixe-me saber a sua ideia
Phan Van Linh
1

Eu sei que isso é um tópico antigo, mas apenas no caso de alguém tropeçar nele.

TokenAuthenticator depende de uma classe de serviço. A classe de serviço depende de uma instância OkHttpClient. Para criar um OkHttpClient, preciso do TokenAuthenticator. Como posso quebrar esse ciclo? Dois OkHttpClients diferentes? Eles terão diferentes pools de conexão ..

Eu estava enfrentando o mesmo problema, mas queria criar apenas um OkHttpClient porque não acho que precise de outro apenas para o próprio TokenAuthenticator, estava usando o Dagger2, então acabei fornecendo a classe de serviço como Lazy injetado no TokenAuthenticator, você pode ler mais sobre a injeção Lazy no punhal 2 aqui , mas é como dizer basicamente ao Dagger para NÃO criar o serviço necessário pelo TokenAuthenticator imediatamente.

Você pode consultar este encadeamento SO para obter um código de exemplo: Como resolver uma dependência circular enquanto ainda usa o Dagger2?

Boda
fonte
0

Você pode tentar criar uma classe base para todos os seus carregadores na qual seria capaz de capturar uma exceção específica e, em seguida, agir conforme necessário. Faça com que todos os seus diferentes carregadores se estendam da classe base, a fim de espalhar o comportamento.

k3v1n4ud3
fonte
A adaptação não funciona dessa maneira. Ele usa anotações Java e interfaces para descrever uma chamada API
Daniel Zolnai
Eu sei como a atualização funciona, mas você ainda está "encapsulando" suas chamadas de API em um AsynTask, não é?
k3v1n4ud3
Não, eu uso as chamadas com um retorno de chamada, para que sejam executadas de forma assíncrona.
Daniel Zolnai
Em seguida, você provavelmente pode criar uma classe de retorno de chamada base e fazer com que todos os seus retornos a estendam.
K3v1n4ud3 17/03
2
Alguma solução para isso? É exatamente o meu caso aqui. = /
Hugo Nogueira
0

Após uma longa pesquisa, eu customizei o cliente Apache para lidar com o Refreshing AccessToken For Retrofit No qual você envia o token de acesso como parâmetro.

Inicie seu adaptador com o cliente persistente de cookie

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

Cookie Cliente persistente que mantém cookies para todas as solicitações e verifica com cada resposta da solicitação, se for de acesso não autorizado ERROR_CODE = 401, atualize o token de acesso e recupere a solicitação, apenas processe a solicitação.

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;
    }
}
Suneel Prakash
fonte
Existe um motivo para você estar usando o ApacheClient em vez da solução sugerida? Não que não seja uma boa solução, mas precisa de muito mais codificação, em comparação ao uso do Interceptors.
Daniel Zolnai
Sua customizada para ser cliente persistente de cookies, mantém a sessão em todos os serviços. Mesmo no Request Intercceptor, você pode adicionar acesso a token nos cabeçalhos. Mas e se você quiser adicioná-lo como um parâmetro? Também OKHTTPClient está tendo limitações. ref: stackoverflow.com/questions/24594823/…
Suneel Prakash:
É mais generalizado para ser usado em qualquer caso 1. Cliente persistente de cookie 2. Aceita solicitações HTTP e HTTPS 3. Atualiza o token de acesso nos parâmetros.
Suneel Prakash
0

O uso de um interceptador (injetar o token) e um autenticador (operações de atualização) fazem o trabalho, mas:

Também tive um problema de chamada dupla: a primeira chamada sempre retornava um 401 : o token não era injetado na primeira chamada (interceptador) e o autenticador era chamado: duas solicitações foram feitas.

A correção foi apenas para reafirmar a solicitação para a compilação no Interceptor:

ANTES:

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

DEPOIS DE:

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

EM UM BLOCO:

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

Espero que ajude.

Edit: Eu não encontrei uma maneira de evitar a primeira chamada para sempre retornar 401 usando apenas o autenticador e nenhum interceptador

Sigrun
fonte
-2

Para quem quisesse resolver chamadas simultâneas / paralelas ao atualizar o token. Aqui está uma solução 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
    }
}
Anders Cheow
fonte