使用Retrofit刷新OAuth令牌,而无需修改所有调用

219
我们在安卓应用中使用Retrofit与OAuth2保护的服务器通信。通过RequestInterceptor,在每个调用中包含访问令牌,一切都工作得很好。 然而,有时访问令牌会过期,需要刷新令牌。当令牌过期时,下一个调用将返回未经授权的HTTP代码,因此很容易监视到。 我们可以按以下方式修改每个Retrofit调用: 在失败的回调中,检查错误代码是否等于Unauthorized,刷新OAuth令牌,然后重复Retrofit调用。 但是,这样做需要修改所有调用,这不是一个易于维护和良好的解决方案。 是否有一种方法可以在不修改所有Retrofit调用的情况下实现这一点?

1
这看起来与我的另一个问题相关。我很快会再次研究它,但一种可能的方法是包装OkHttpClient。类似于这样:https://github.com/pakerfeldt/signpost-retrofit此外,由于我正在使用Retrofit和RoboSpice,创建一个基本的Request类也可能是另一种可能的方法。不过,您可能需要想出如何在没有上下文的情况下实现您的流程,可能使用Otto/EventBus。 - Hassan Ibraheem
1
你可以fork它,然后删除不需要的情况。我今天可能会研究一下,并在这里发布,如果我实现了可能解决我们问题的东西。 - Daniel Zolnai
2
结果证明,该库无法处理刷新令牌,但是它给了我一个想法。我写了一个小的代码片段,虽然没有经过测试,但理论上应该可以工作:https://gist.github.com/ZolnaiDani/9710849 - Daniel Zolnai
3
我能想到的解决方案是:将changeTokenInRequest(...)方法设为同步方法,并在第一行添加检查上次刷新令牌的时间。如果距离上次刷新令牌的时间间隔很短(毫秒级别),则不刷新令牌。你也可以设置这个时间间隔为1小时左右,以防止在令牌过期之外出现其他问题时不断请求新的令牌。 - Daniel Zolnai
2
Retrofit 1.9.0 刚刚增加了对 OkHttp 2.2 的支持,其中包括拦截器。这应该会让你的工作更轻松。更多信息请参见:https://github.com/square/retrofit/blob/master/CHANGELOG.md#version-190-2015-01-07 和 https://github.com/square/okhttp/wiki/Interceptors 不过,你也需要扩展 OkHttp 来使用它们。 - Daniel Zolnai
显示剩余9条评论
11个回答

274

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

目前,处理身份验证的最佳方法是使用新的Authenticator API,该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;
    }

与拦截器一样,您可以将 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);

14
@Jdruwe 看起来这个代码会失败一次,然后它会发出请求。但如果你添加一个拦截器,其唯一目的是始终添加访问令牌(无论是否过期),那么只有在收到 401 响应码时才会调用该拦截器,而这只会在令牌过期时发生。 - narciero
83
TokenAuthenticator 依赖于一个 service 类。service 类依赖于一个 OkHttpClient 实例。要创建一个 OkHttpClient,我需要 TokenAuthenticator。如何打破这个循环依赖呢?使用两个不同的 OkHttpClient 吗?它们将拥有不同的连接池... 如何打破这个循环依赖呢?可以使用依赖注入,将 OkHttpClient 的实例注入到 service 中,然后再将 service 的实例注入到 TokenAuthenticator 中。这样就可以避免循环依赖了。如果需要使用多个 OkHttpClient 实例,则可以分别创建它们并配置不同的连接池。 - Brais Gabin
17
有许多需要刷新令牌的并行请求,会同时发出许多刷新令牌请求,该如何避免?请问您有任何建议吗? - Igor Kostenko
9
@PaulWoitaschek 保罗,不要忘记身份验证过程中 Authenticator 不是唯一的部分。你需要有两个部分:拦截器注入正确的 access_token,然后在没有有效 access_token 的情况下使用 Authenticator :) 所以只有当任何网络请求返回 401(或 407)时,你才会触发 Authenticator。此时你应该通过运行刷新流程来获取新的 access_token,随后的请求只要拦截器使用正确/更新的 access_token 就可以正常工作。 - Dariusz Wiechecki
19
好的,@Ihor的问题的解决方案可能是同步 Authenticator 内部的代码。这在我的情况下解决了问题。在 Request authenticate(...) 方法中:
  • 进行任何初始化工作
  • 开始同步块 ( synchronized(MyAuthenticator.class) { ... } )
  • 在该块中检索当前访问和刷新令牌
  • 检查失败的请求是否使用最新的访问令牌 (resp.request().header("Authorization")) - 如果没有,只需使用更新后的访问令牌再次运行它
  • 否则运行刷新令牌流程
  • 更新/持久化已更新的访问和刷新令牌
  • 完成同步块
  • 重新运行
- Dariusz Wiechecki
显示剩余30条评论

73
如果您正在使用 Retrofit >= 1.9.0,则可以利用OkHttp的Interceptor,该拦截器在OkHttp 2.2.0中引入。您需要使用一个Application Interceptor,它允许您重试并进行多个调用
您的拦截器可能会像这样的伪代码:
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并将拦截器添加为应用程序拦截器
    OkHttpClient okHttpClient = new OkHttpClient();
    okHttpClient.interceptors().add(new CustomInterceptor());

最后,在创建 RestAdapter 时,请使用此 OkHttpClient
    RestService restService = new RestAdapter().Builder
            ...
            .setClient(new OkClient(okHttpClient))
            .create(RestService.class);

警告:正如Square公司的Jesse Wilson在这里提到的那样,这是一种危险的强大力量。

话虽如此,我肯定认为现在处理这类问题的最佳方式。如果您有任何疑问,请在评论中不要犹豫地提出。


3
当Android不允许在主线程上进行网络调用时,您如何在Android中实现同步调用?我遇到了从异步调用返回响应的问题。 - lgdroid57
1
@lgdroid57 你说得对,因此当你开始触发拦截器的原始请求时,应该已经在另一个线程上了。 - theblang
4
这个方法很有效,但我必须确保关闭以前的响应,否则会泄露以前的连接。 ... 最终,将新请求构建完成后,需要关闭先前的响应,然后返回处理新请求的结果。 - DallinDyer
谢谢!我遇到了一个问题,即原始请求的回调接收到了一个“closed”错误消息而不是原始响应,因为拦截器中的主体已被消耗。我能够解决成功响应的情况,但我无法解决失败响应的情况。有任何建议吗? - lgdroid57
谢谢@mattblang,看起来很不错。一个问题:即使在重试时,请求回调是否有保证被调用? - Luca Fagioli
我需要问一个问题。它是如何工作的?如果您发出请求,首先拦截器会发出请求,如果其代码不等于未授权(即401),则进行正常请求。如果等于401,则刷新令牌并执行正常请求。如果它按照我想的那样做,所有请求计数都会加倍。我错了吗? - Yasin Kaçmaz

29

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

如果你有一个需要在你的Authenticator中使用的RetrofitTokenService,但你只想设置一个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类是在哪里创建的? - Yogesh Suthar
@YogeshSuthar 这是一个Retrofit服务 - 请参见相关问题 - David Rawson
谢谢,你能提供service.refreshToken().execute();refreshToken()的实现吗?我在任何地方都找不到它的实现。 - Yogesh Suthar
@Yogesh refreshToken方法来自您的API。无论您如何调用刷新令牌(可能是使用用户名和密码的调用?),或者可能是提交令牌并响应新令牌的请求。 - David Rawson
为什么不直接手动注入TokenService而不是TokenServiceHolder呢? - Abbas

15

使用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 class

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时,向OKHttpClient添加InterceptorAuthenticator

演示

https://github.com/PhanVanLinh/AndroidMVPKotlin

注意事项

  • 示例API getImage()返回401错误代码
  • TokenAuthenticator内的authenticate方法将被触发
  • 同步调用noneAuthAPI.refreshToken(...)
  • noneAuthAPI.refreshToken(...)响应之后 ->新令牌将添加到标头中
  • getImage()将使用新标头自动调用HttpLogging 不会记录此调用)(AuthInterceptor内的intercept 不会被调用
  • 如果getImage()仍然失败并显示401错误,则TokenAuthenticator内的authenticate方法将再次触发,然后它将抛出有关多次调用方法的错误(java.net.ProtocolException: Too many follow-up requests)。您可以通过计数响应来防止它。例如,如果您在3次重试后在authenticatereturn null,则getImage()完成并返回401响应

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

希望这有所帮助

此解决方案使用2个不同的OkHttpClients,正如您的ServiceGenerator类中所示。 - SpecialSnowflake
@SpecialSnowflake 你是正确的。如果你按照我的解决方案,你需要创建2个OkHttp,因为我创建了2个服务(oauth和未认证)。我认为这不会引起任何问题。让我知道你的想法。 - Linh

4
如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是否安全,还是只是为了简单起见? - gts13
你最后在 authenticate 函数中返回 null,对吧? - gts13
关于 runBlocking。这里不需要使用它。请改用 Call<TokenResponse>.execute()。协程很好,但对于完全同步的任务没有必要使用它,因为它会引入不必要的依赖关系。 - Alexander Skvortsov

3

我知道这是一个旧帖子,但以防万一有人碰巧看到了它。

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

我面临着同样的问题,但我只想创建一个OkHttpClient,因为我认为我不需要另一个仅用于TokenAuthenticator本身的OkHttpClient,我正在使用Dagger2,所以最终我提供了服务类作为TokenAuthenticator中的延迟注入,您可以在这里了解有关Dagger 2中的Lazy注入的更多信息,但基本上就像告诉Dagger不要立即创建TokenAuthenticator所需的服务。

您可以参考此SO线程以获取示例代码:如何在仍然使用Dagger2的情况下解决循环依赖关系?


1

使用一个拦截器(注入令牌)和一个身份验证器(刷新操作)可以完成工作,但是:

我还遇到了双重调用问题:第一次调用总是返回401:在第一次调用时未注入令牌(拦截器),并且调用了身份验证器:进行了两个请求。

解决方法就是将请求重新分配给内置的拦截器:

之前:

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

AFTER:
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的第一次调用的方法。

0
经过长时间的研究,我定制了Apache客户端以处理Retrofit中的AccessToken刷新,在其中您将访问令牌作为参数发送。
使用Cookie Persistent Client初始化您的适配器。
restAdapter = new RestAdapter.Builder()
                .setEndpoint(SERVER_END_POINT)
                .setClient(new CookiePersistingClient())
                .setLogLevel(RestAdapter.LogLevel.FULL).build();

Cookie Persistent客户端维护所有请求的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而不是建议的解决方案,有什么原因吗?并不是说它不是一个好的解决方案,但与使用拦截器相比,需要编写更多的代码。 - Daniel Zolnai
它被定制为cookie持久客户端,可以在整个服务期间保持会话。即使在请求拦截器中,您也可以在标头中添加访问令牌。但是如果您想将其作为参数添加呢? 此外,OKHTTPClient存在一些限制。参考:https://dev59.com/GoHba4cB1Zd3GeqPO0R1 - Suneel Prakash
  1. Cookie 持久客户端
  2. 接受 HTTP 和 HTTPS 请求
  3. 在参数中更新访问令牌。
- Suneel Prakash

0

这是我的代码,对我很有效。可能对某些人有帮助。

   class AuthenticationInterceptorRefreshToken @Inject 
   constructor( var hIltModules: HIltModules,) : Interceptor {

   @Throws(IOException::class)
   override fun intercept(chain: Interceptor.Chain): Response {

  val originalRequest = chain.request()
  val response = chain.proceed(originalRequest)

  if (response.code == 401) {
    synchronized(this) {
        val originalRequest = chain.request()
        val authenticationRequest = originalRequest.newBuilder()
            .addHeader("refreshtoken", " $refreshToken")
            .build()
        val initialResponse = chain.proceed(authenticationRequest)

        when (initialResponse.code) {

            401 -> {
                val responseNewTokenLoginModel = runBlocking {
                    hIltModules.provideAPIService().refreshToken()
                }

                when (responseNewTokenLoginModel.statusCode) {
                    200 -> {
                        refreshToken = responseNewTokenLoginModel.refreshToken
                        access_token = responseNewTokenLoginModel.accessToken

                        val newAuthenticationRequest = originalRequest.newBuilder()
                            .header("refreshtoken",
                                " $refreshToken")
                            .build()
                        return chain.proceed(newAuthenticationRequest)
                    }
                    else -> {
                        return null!!
                    }
                }
            }
            else -> return initialResponse
        }
    }
}; return response

}


0
你可以尝试创建一个基类,用于所有加载器,在其中你将能够捕获特定的异常并根据需要采取行动。 让所有不同的加载器都继承自这个基类,以便传播这种行为。

Retrofit 不是那样工作的。它使用 Java 注解和接口来描述 API 调用。 - Daniel Zolnai
我知道如何使用Retrofit,但你仍然在AsyncTask中“包装”你的API调用,是吗? - k3v1n4ud3
不,我使用带有回调函数的调用,因此它们可以异步运行。 - Daniel Zolnai
是的,我也考虑过这个问题,但主要问题是,我需要再次使用相同参数调用我的方法。但是我该如何将该方法提供给自定义回调类呢? - Daniel Zolnai
2
有什么解决办法吗?这正是我的情况。 =/ - Hugo Nogueira
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接