Android Retrofit 2.0 刷新令牌

Posted

技术标签:

【中文标题】Android Retrofit 2.0 刷新令牌【英文标题】:Android Retrofit 2.0 Refresh Tokens 【发布时间】:2016-10-12 09:23:45 【问题描述】:

我正在使用 Retrofit 2.0Jackson 转换器与 Rest API 进行通信。一些请求需要授权令牌。如果我拥有的令牌已过期,我需要用另一个请求刷新它们并重复上一个因此而失败的请求。

我的问题:我需要每次都手动完成还是有什么方法可以自动完成?

这是我目前实现它的方式:

TrackerService

public interface TrackerService 

    @POST("auth/sendPassword")
    Call<ResponseMessage> sendPassword(@Header("app-type") String appType, 
                                       @Body User userMobile);

    @FormUrlEncoded
    @POST("oauth/token")
    Call<TokenResponse> oathToken(@Field("client_id") String clientId,
                                  @Field("client_secret") String clientSecret,
                                  @Field("grant_type") String grantType,
                                  @Field("username") String username,
                                  @Field("password") String password);

    @FormUrlEncoded
    @POST("oauth/token")
    Call<TokenResponse> refreshToken(@Field("client_id") String clientId,
                                     @Field("client_secret") String clientSecret,
                                     @Field("grant_type") String grantType,
                                     @Field("refresh_token") String username);


    @PUT("me/profile")
    Call<Profile> updateProfile(@Header("app-type") String appType,
                                @Header("Authorization") String token,
                                @Body Profile profile);


服务网关

public class ServiceGateway 

    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
    private static Retrofit retrofit;

    public static <S> S createService(Class<S> serviceClass) 
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(20 * 1000, TimeUnit.MILLISECONDS)
                .writeTimeout(20 * 1000, TimeUnit.MILLISECONDS)
                .readTimeout(20 * 1000, TimeUnit.MILLISECONDS)
                .addInterceptor(interceptor).build();

        Retrofit.Builder builder =
                new Retrofit.Builder()
                        .baseUrl(BASE_URL)
                        .addConverterFactory(JacksonConverterFactory.create());

        retrofit = builder.client(httpClient.build())
                .client(client)
                .build();
        return retrofit.create(serviceClass);
    

    public static Retrofit getRetrofit() 
        return retrofit;
    

令牌过期时如何调用函数和处理函数

 trackerService = ServiceGateway.createService(TrackerService.class);

    Call<Profile> call = trackerService.updateProfile(getString(R.string.app_type), "Bearer " + userPrefs.accessToken().get(),
            new Profile(trimedInvitationMessage, title,
            String.valueOf(selectedCountry.getCountryCode()), mobilePhone, countryISO, fullName));

    call.enqueue(new Callback<Profile>() 
        @Override
        public void onResponse(Call<Profile> call, Response<Profile> response) 
            if (response.body() != null) 


             else 
                if (response.raw().code() == 401) 
                    Call<TokenResponse> refreshTokenCall = trackerService.refreshToken(userPrefs.clientId().get(),
            userPrefs.clientSecret().get(), "refresh_token", userPrefs.refreshToken().get());
                    refreshTokenCall.enqueue(new Callback<TokenResponse>() 
                        @Override
                        public void onResponse(Call<TokenResponse> call, Response<TokenResponse> response) 
                            if (response.body() != null) 

                                updateAdviserProfile(trimedInvitationMessage, title, mobilePhone, countryISO, fullName);
                             else 
                                userPrefs.clear();
                                Intent intent = new Intent(WelcomeActivity_.launcher(EditProfileActivity.this));
                                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
                                intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                startActivity(intent);
                                startActivity(WelcomeActivity_.launcher(EditProfileActivity.this));
                            
                        

                        @Override
                        public void onFailure(Call<TokenResponse> call, Throwable t) 

                        
                    );
                 else if (response.raw().code() == 422)
            
        

        @Override
        public void onFailure(Call<Profile> call, Throwable t) 
        
    );

【问题讨论】:

【参考方案1】:

我从 2-3 个月前开始搜索这个主题,发现 OkHttp's Authenticator。你可以使用它。这里有一个链接:refreshing-oauth-token-using-retrofit-without-modifying-all-calls

它的工作原理是这样的:如果您的请求返回 401,那么 Authenticator 将进入并刷新您的令牌。但不要忘记return null 或设置任何尝试限制。如果你不限制,它会在你的刷新请求失败时尝试多次刷新。此外,刷新令牌时发出同步请求。

另外,我有一个关于刷新 Oauth2 令牌的问答(均由我自己编写):

问题:android-retrofit2-refresh-oauth-2-token

回答:android-retrofit2-refresh-oauth-2-token-answer

另外:例如,如果您有一个令牌并且您需要每 3 小时刷新一次。你也可以写一个Interceptor。在Interceptor:比较时间并刷新您的令牌,而不会收到任何401 响应。

Square 的 Interceptor 文档:OkHttp Interceptors

Square 的 Authenticator 文档:OkHttp handling-authentication

我知道这里没有代码,但请查看链接并编辑您的问题,然后我会尽力帮助您。

【讨论】:

PLUS ONE 用简单的语言解释了OKHttp Authenticator的作用。这很有帮助【参考方案2】:

服务器返回 401 Unauthorized 时调用 authenticate() 方法。

用于调用 ApiFactory.retrofit("url").create(PostDataInterface::class.java) .refreshToken(refreshTokenRequest)),我们正在使用 execute() 使其成为同步调用。

如果刷新令牌状态为 0。添加您的功能以注销用户。

interface PostDataInterface 
@POST("refreshUserToken")
fun refreshToken(@Body refreshTokenRequest: RefreshTokenRequest?): Call<RefreshTokenResponse?>?

class TokenAuthenticator : Authenticator 
override fun authenticate(route: Route?, response: Response): Request? 

    // This is a synchronous call
    val updatedToken = getNewToken()

    return updatedToken?.let 
        response.request.newBuilder().header("Authorization", it)
            .build()
    


private fun getNewToken(): String? 

    val refreshTokenRequest = RefreshTokenRequest(SharedPreferenceHelper.refreshToken)
    val call = ApiFactory.retrofit(BuildConfig.BASEURL).create(PostDataInterface::class.java)
        .refreshToken(refreshTokenRequest)
    val authTokenResponse = call?.execute()?.body()

    if (authTokenResponse?.status == 0)
        //Logout User
        AuthUtility.logout(true)
    

    return authTokenResponse?.data?.token


在 Okhttp 客户端中添加验证器

private val client =
        OkHttpClient().newBuilder()
            .authenticator(TokenAuthenticator())
            ...
            .build()
  

【讨论】:

Ok 但是获取新的刷新令牌后如何重新调用API? 它会自动调用【参考方案3】:

这里是刷新令牌认证器的实现

class TokenAuthenticator(
    val sharedPrefsHelper: SharedPrefsHelper,
    private val identityService: IdentityService
) : Authenticator 

    override fun authenticate(route: Route?, response: Response): Request? 
        Log.d("TokenAuth Request:", "$response.body")
        val refreshToken = sharedPrefsHelper[SharedPrefsHelper.PREF_KEY_AUTH_REFRESH_TOKEN, null]
        if (refreshToken.isNullOrEmpty().not()) 
            val requestFields = mutableMapOf<String, String>()
            requestFields["refresh_token"] = refreshToken!!
            requestFields["grant_type"] = "refresh_token"
            try 
                val tokenResponse = runBlocking 
                    identityService.getAuthToken(requestFields)
                
                Log.d("TokenAuth Success:", "$tokenResponse")
                tokenResponse.accessToken.let  accessToken ->
                    sharedPrefsHelper.put(
                        SharedPrefsHelper.PREF_KEY_AUTH_TOKEN,
                        accessToken
                    )
                    sharedPrefsHelper.put(
                        SharedPrefsHelper.PREF_KEY_AUTH_REFRESH_TOKEN,
                        tokenResponse.refreshToken
                    )

                    return response.request.newBuilder()
                        .header("Authorization", "Bearer $accessToken")
                        .build()
                
             catch (e: Exception) 
                Log.d("TokenAuth Error:", "$e")
            
        
        return null
    

使用构建器配置它 -

  return OkHttpClient.Builder()
            .authenticator(TokenAuthenticator(sharedPrefsHelper, identityBaseUrl))
            .addInterceptor(httpLoggingInterceptor)
            .addInterceptor(requestInterceptor).addInterceptor(logging)
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .build()

也正如@Yasin 建议的那样 -

不要忘记返回 null 或设置任何尝试限制。如果你不限制,它会在你的刷新请求失败时尝试多次刷新。此外,刷新令牌时发出同步请求。

【讨论】:

以上是关于Android Retrofit 2.0 刷新令牌的主要内容,如果未能解决你的问题,请参考以下文章

需要调用一个api来使用retrofit在android mvvm中刷新令牌,在哪里编写逻辑?

Android OkHttp,刷新过期令牌

OkHttp 和 Retrofit,用并发请求刷新令牌

如何在 Spotify SDK for Android 上刷新访问令牌?

在改造并行网络调用中处理后台刷新令牌调用

存储位置 - OAuth 2.0 中的访问令牌和刷新令牌