ChatGPT解决这个技术问题 Extra ChatGPT

使用 Retrofit 刷新 OAuth 令牌而不修改所有调用

我们在我们的 Android 应用程序中使用 Retrofit 来与 OAuth2 安全服务器进行通信。一切正常,我们使用 RequestInterceptor 在每次调用中包含访问令牌。但是有时访问令牌将过期,并且需要刷新令牌。当令牌过期时,下一次调用将返回未经授权的 HTTP 代码,因此很容易监控。我们可以通过以下方式修改每个 Retrofit 调用:在失败回调中,检查错误代码,如果等于 Unauthorized,则刷新 OAuth 令牌,然后重复 Retrofit 调用。但是,为此,应该修改所有调用,这不是一个易于维护且良好的解决方案。有没有办法在不修改所有改造调用的情况下做到这一点?

这看起来与我的 other question 相关。我很快会再次研究它,但一种可能的方法是包装 OkHttpClient。像这样:github.com/pakerfeldt/signpost-retrofit 另外,由于我正在使用带有 Retrofit 的 RoboSpice,创建一个基本的 Request 类也可能是另一种可能的方法。可能你必须弄清楚如何在没有上下文的情况下实现你的流程,也许使用 Otto/EventBus。
好吧,您可以分叉它,并删除不需要的案例。我可能会在今天对此进行研究,如果我取得了可以解决我们问题的东西,请在此处发布。
原来,图书馆没有处理刷新令牌,但给了我一个想法。我对一些 !untested 代码做了一个小要点,但理论上,我认为它应该可以工作:gist.github.com/ZolnaiDani/9710849
@neworld 我能想到的解决方案:使 changeTokenInRequest(...) 同步,并在第一行检查上次刷新令牌的时间。如果只是几秒(毫秒)前,请不要刷新令牌。您还可以将此时间范围设置为 1 小时左右,以在令牌已过时之外出现其他问题时停止不断请求新令牌。
Retrofit 1.9.0 刚刚添加了对带有拦截器的 OkHttp 2.2 的支持。这应该会让你的工作轻松很多。有关详细信息,请参阅:github.com/square/retrofit/blob/master/…github.com/square/okhttp/wiki/Interceptors 不过,您也必须为这些扩展 OkHttp。

C
Community

请不要使用 Interceptors 来处理身份验证。

目前,处理身份验证的最佳方法是使用专为 this purpose 设计的新 Authenticator API。

当响应401 Not Authorised重试上次失败的请求时,OkHttp 将自动询问 Authenticator 提供凭据。

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

以与 Interceptors 相同的方式将 Authenticator 附加到 OkHttpClient

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

这是否意味着每个请求都会失败 1 次,还是在请求时添加令牌?
@Jdruwe 看起来这段代码会失败 1 次,然后它会发出请求。但是,如果您添加一个拦截器,其唯一目的是始终添加一个访问令牌(无论它是否过期),那么只有在收到 401 时才会调用它,这只会在该令牌过期时发生。
TokenAuthenticator 依赖于一个 service 类。 service 类依赖于 OkHttpClient 实例。要创建 OkHttpClient,我需要 TokenAuthenticator。我怎样才能打破这个循环?两个不同的OkHttpClient?他们将有不同的连接池......
需要刷新令牌的并行请求怎么样?同时会有多个刷新令牌请求。如何避免?
好的,所以@Ihor 问题的解决方案可能是同步Authenticator 中的代码。它解决了我的问题。在 Request authenticate(...) 方法中: - 做任何初始化工作 - 启动同步块( synchronized(MyAuthenticator.class) { ... } ) - 在该块中检索当前访问和刷新令牌 - 检查失败的请求是否使用最新访问令牌 (resp.request().header("Authorization")) - 如果不只是使用更新的访问令牌再次运行它 - 否则运行刷新令牌流 - 更新/保持更新的访问和刷新令牌 - 完成同步块 - 重新运行
E
EpicPandaForce

如果您使用的是 Retrofit >= 1.9.0,那么您可以使用 OkHttp 2.2.0 中引入的 OkHttp's new Interceptor。您可能想要使用 Application Interceptor,它允许您使用 retry 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) {
            // close previous response
            response.close()

            // 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 并将拦截器添加为 Application Interceptor

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

最后,在创建 RestAdapter 时使用此 OkHttpClient

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

警告:正如 Jesse Wilson(来自 Square)提到的 here,这是一种危险的力量。

话虽如此,我绝对认为这是现在处理此类事情的最佳方式。如果您有任何问题,请随时在评论中提问。


当 Android 不允许在主线程上进行网络调用时,如何在 Android 中实现同步调用?我遇到了从异步调用返回响应的问题。
@lgdroid57您是对的,因此当您启动触发拦截器运行的原始请求时,您应该已经在另一个线程上。
这很好用,除非我必须确保关闭先前的响应,否则我会泄漏先前的连接... final Request newRequest = request.newBuilder()....build(); response.body().close(); return chain.proceed(newRequest);
谢谢!我遇到了一个问题,原始请求的回调收到“关闭”的错误消息,而不是原始响应,因为在拦截器中消耗了正文。我能够为成功的响应解决此问题,但我无法为失败的响应解决此问题。有什么建议么?
谢谢@mattblang,它看起来不错。一个问题:是否保证即使在重试时也会调用请求回调?
D
David Rawson

TokenAuthenticator 依赖于一个服务类。服务类依赖于 OkHttpClient 实例。要创建 OkHttpClient,我需要 TokenAuthenticator。我怎样才能打破这个循环?两个不同的 OkHttpClients?他们将有不同的连接池..

例如,如果您在 Authenticator 中有需要的 Retrofit TokenService,但您只想设置一个 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 或类似的依赖注入框架,那么 this question 的答案中有一些示例


TokenService 类在哪里创建?
@YogeshSuthar 这是一项改造服务 - 请参阅 the related question
谢谢,您能否提供来自 service.refreshToken().execute();refreshToken() 的实现。无法在任何地方找到它的实现。
@Yogesh refreshToken 方法来自您的API。无论您调用什么来刷新令牌(可能是带有用户名和密码的调用?)。或者可能是您提交令牌并且响应是新令牌的请求
为什么不直接注入(手动)TokenService 而不是 TokenServiceHolder
L
Linh

像@theblang answer 一样使用 TokenAuthenticator 是处理 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())
    }
}

最后,在创建服务 PotoAuthApi 时将 InterceptorAuthenticator 添加到您的 OKHttpClient

演示

https://github.com/PhanVanLinh/AndroidMVPKotlin

笔记

示例 API getImage() 返回 401 错误代码

TokenAuthenticator 中的 authenticate 方法将被触发

同步调用 noneAuthAPI.refreshToken(...)

在 noneAuthAPI.refreshToken(...) 响应后 -> 新令牌将添加到标头

getImage() 将使用新标头自动调用(HttpLogging 不会记录此调用)(在 AuthInterceptor 内拦截不会调用)

如果 getImage() 仍然失败并出现错误 401,则 TokenAuthenticator 中的身份验证方法将再次触发 AGAIN 和 AGAIN 然后它会多次抛出有关调用方法的错误(java.net.ProtocolException: Too many follow-up requests)。您可以通过计数响应来防止它。例如,如果您在重试 3 次后在 authenticate 中返回 null,则 getImage() 将完成并返回响应 401

如果 getImage() 响应成功 => 我们将正常生成结果(就像您调用 getImage() 时没有错误一样)

希望有帮助


此解决方案使用 2 个不同的 OkHttpClient,这在您的 ServiceGenerator 类中很明显。
@SpecialSnowflake 你是对的。如果您遵循我的解决方案,则需要创建 2 个 OkHttp,因为我创建了 2 个服务(oauth 和 none auth)。我认为这不会造成任何问题。让我知道你的想法
B
Boda

我知道这是一个旧线程,但以防万一有人偶然发现它。

TokenAuthenticator 依赖于一个服务类。服务类依赖于 OkHttpClient 实例。要创建 OkHttpClient,我需要 TokenAuthenticator。我怎样才能打破这个循环?两个不同的 OkHttpClients?他们将有不同的连接池..

我遇到了同样的问题,但我只想创建一个 OkHttpClient 因为我认为我不需要另一个用于 TokenAuthenticator 本身,我使用的是 Dagger2,所以我最终将服务类提供为 Lazy在 TokenAuthenticator 中注入,您可以阅读更多关于 Dagger 2 here 中的惰性注入的信息,但这基本上就像是对 Dagger 说 立即创建 TokenAuthenticator 所需的服务.

您可以参考此 SO 线程以获取示例代码:How to resolve a circular dependency while still using Dagger2?


J
Jorge Casariego

正如 Brais Gabin 在评论中所说,我遇到了 TokenAuthenticator 依赖于服务类的问题。服务类取决于 OkHttpClient 实例,要创建 OkHttpClient,我需要 TokenAuthenticator

那么我是如何打破这个循环的呢?

我创建了一个新的 okHttpClient 对象和一个新的 Retrofit 对象,并使用该对象进行调用以使用 refreshToken 获取新令牌(检查 getUpdatedToken() 函数)

class TokenAuthenticator : Authenticator {

    override fun authenticate(route: Route?, response: Response): Request? {
        return runBlocking {

            // 1. Refresh your access_token using a synchronous api request
           val response = getUpdatedToken(refreshToken)

           //2. In my case here I store the new token and refreshToken into SharedPreferences

           response.request.newBuilder()
                        .header("Authorization", "Bearer   ${tokenResponse.data?.accessToken}")
                        .build()

           // 3. If there's any kind of error I return null
           
        }
    }

    private suspend fun getUpdatedToken( refreshToken: String): TokenResponse {
        val okHttpClient = OkHttpClient().newBuilder()
            .addInterceptor(errorResponseInterceptor)
            .build()

        val retrofit = Retrofit.Builder()
            .baseUrl(BuildConfig.BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()


        val service = retrofit.create(RefreshTokenApi::class.java)
        return service.refreshToken(refreshToken)

    }

}

刷新令牌API

interface RefreshTokenApi {

    @FormUrlEncoded
    @POST("refreshToken")
    suspend fun refreshToken(
        @Field("refresh_token") refreshToeken: String
    ): TokenResponse
}

在这个项目中,我使用的是 Koin,我是这样配置的:

object RetrofigConfig {
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(BuildConfig.BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
    }

    fun provideOkHttpClient(
        tokenAuthenticator: TokenAuthenticator
    ): OkHttpClient {

        return OkHttpClient().newBuilder()
            .authenticator(tokenAuthenticator)
            .build()
    }

    fun provideServiceApi(retrofit: Retrofit): ServiceApi {
        return retrofit.create(ServiceApi::class.java)
    }
}

重要的一行是 OkHttpClient().newBuilder().authenticator(tokenAuthenticator)

因为这是我第一次实现这个我不知道这是否是最好的方式,但它是我项目中的工作方式。


使用 runBlocking 是否安全,还是只是为了简单起见?
我想你在 authenticate 函数的末尾返回 null ,对吧?
关于 runBlocking。不需要在这里使用它。请改用 Call.execute()。协程很好,但不需要将它用于完全同步的任务,因为它引入了不必要的依赖关系。
S
Sigrun

使用一个拦截器(注入令牌)和一个验证器(刷新操作)来完成这项工作,但是:

我也遇到了双重调用问题:第一次调用总是返回 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 只使用身份验证器而不使用拦截器


k
k3v1n4ud3

您可以尝试为所有加载程序创建一个基类,您可以在其中捕获特定异常,然后根据需要执行操作。让所有不同的加载器从基类扩展,以传播行为。


改造不是那样工作的。它使用 java 注释和接口来描述 API 调用
我知道改造是如何工作的,但你仍然在 AsynTask 中“包装”你的 API 调用,不是吗?
不,我将调用与回调一起使用,因此它们异步运行。
然后您可能可以创建一个基本回调类并让您的所有回调扩展它。
有什么解决办法吗?这正是我的情况。 =/
S
Suneel Prakash

经过长时间的研究,我定制了 Apache 客户端来处理 Refreshing AccessToken For Retrofit 在其中您将访问令牌作为参数发送。

使用 cookie 持久客户端启动您的适配器

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

Cookie 持久客户端,它为所有请求维护 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 持久客户端,在整个服务中维护会话。即使在请求拦截器中,您也可以在标头中添加访问令牌。但是,如果您想将其添加为参数怎么办? OKHTTPClient 也有限制。参考:stackoverflow.com/questions/24594823/…
在任何情况下使用它更通用 1. Cookie 持久客户端 2. 接受 HTTP 和 HTTPS 请求 3. 更新 Params 中的访问令牌。
A
Anders Cheow

对于任何想要在刷新令牌时解决并发/并行调用的人。这是一个解决方法

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