安卓Retrofit 2.0刷新令牌

30
我正在使用Retrofit 2.0和Jackson转换器与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) {
        }
    });
3个回答

52

我在2-3个月前搜索了这个主题,找到了OkHttp的认证器。你可以使用它。这里有一个链接: 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小时刷新一次它,您也可以编写一个拦截器。在拦截器中:比较时间并刷新您的令牌,而不需要获取任何401响应。

Interceptor的Square文档:OkHttp Interceptors

Authenticator的Square文档:OkHttp handling-authentication

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


1
PLUS ONE 是为了用简单明了的语言解释 OKHttp Authenticator 的作用而设立的。这将会很有帮助。 - Rinav

2
这里是刷新令牌验证器实现。
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或设置任何尝试限制。如果不进行限制,当您的刷新请求失败时,它将尝试多次刷新。此外,在刷新令牌时进行同步请求。


1

当服务器返回401未经授权时,将调用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()
  

好的,但是在获取新的刷新令牌后如何重新调用API? - Kishan Solanki
1
它将自动调用 - Dino Sunny

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