We are using Retrofit in our Android app, to communicate with an OAuth2 secured server. Everything works great, we use the RequestInterceptor to include the access token with each call. However there will be times, when the access token will expire, and the token needs to be refreshed. When the token expires, the next call will return with an Unauthorized HTTP code, so that's easy to monitor. We could modify each Retrofit call the following way: In the failure callback, check for the error code, if it equals Unauthorized, refresh the OAuth token, then repeat the Retrofit call. However, for this, all calls should be modified, which is not an easily maintainable, and good solution. Is there a way to do this without modifying all Retrofit calls?


当前回答

我知道这是一条老帖子,但以防有人无意中发现。

TokenAuthenticator依赖于服务类。服务类依赖于OkHttpClient实例。要创建OkHttpClient,我需要TokenAuthenticator。我该如何打破这个循环?两个不同的OkHttpClients?它们将有不同的连接池..

我也面临着同样的问题,但我想只创建一个OkHttpClient因为我不认为我需要另一个TokenAuthenticator本身,我用Dagger2,所以我最终提供服务类TokenAuthenticator懒惰的注入,你可以阅读更多关于懒惰匕首2中注入,但基本上就像说匕首TokenAuthenticator所需的不去创建服务。

你可以参考这个SO线程的示例代码:如何解决一个循环依赖,同时仍然使用Dagger2?

其他回答

TokenAuthenticator依赖于服务类。服务类依赖于OkHttpClient实例。要创建OkHttpClient,我需要TokenAuthenticator。我该如何打破这个循环?两个不同的OkHttpClients?它们将有不同的连接池..

如果你有一个你需要在你的Authenticator内部的Retrofit TokenService,但是你只想建立一个OkHttpClient,你可以使用TokenServiceHolder作为TokenAuthenticator的依赖项。您必须在应用程序(单例)级别维护对它的引用。如果你使用匕首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或类似的依赖注入框架,这里有一些例子可以回答这个问题

请不要使用拦截器来处理身份验证。

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

当响应为401未授权重试最后一次失败的请求时,OkHttp将自动向验证者请求凭据。

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

将验证器附加到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);

给任何人谁想解决并发/并行调用时刷新令牌。这里有一个变通办法

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

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

我知道这是一条老帖子,但以防有人无意中发现。

TokenAuthenticator依赖于服务类。服务类依赖于OkHttpClient实例。要创建OkHttpClient,我需要TokenAuthenticator。我该如何打破这个循环?两个不同的OkHttpClients?它们将有不同的连接池..

我也面临着同样的问题,但我想只创建一个OkHttpClient因为我不认为我需要另一个TokenAuthenticator本身,我用Dagger2,所以我最终提供服务类TokenAuthenticator懒惰的注入,你可以阅读更多关于懒惰匕首2中注入,但基本上就像说匕首TokenAuthenticator所需的不去创建服务。

你可以参考这个SO线程的示例代码:如何解决一个循环依赖,同时仍然使用Dagger2?