【问题标题】:Android Retrofit 2.0 Refresh TokensAndroid Retrofit 2.0 刷新令牌
【发布时间】: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) {
        }
    });

【问题讨论】:

    标签: android jackson retrofit


    【解决方案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 或设置任何尝试限制。如果你不限制,它会在你的刷新请求失败时尝试多次刷新。此外,刷新令牌时发出同步请求。

    【讨论】:

      猜你喜欢
      • 2015-04-01
      • 2015-11-04
      • 2018-03-30
      • 2014-09-22
      • 2015-05-17
      • 1970-01-01
      • 2015-09-29
      • 2012-01-29
      • 2017-01-11
      相关资源
      最近更新 更多