Android Retrofit2 刷新 Oauth 2 令牌 [英] Android Retrofit2 Refresh Oauth 2 Token

查看:39
本文介绍了Android Retrofit2 刷新 Oauth 2 令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 RetrofitOkHttp 库.我有一个 Authenticator 可以在我们收到 401 响应时对用户进行身份验证.

我的build.gradle是这样的:

编译'com.squareup.retrofit2:retrofit:2.0.0-beta4'编译'com.squareup.retrofit2:converter-gson:2.0.0-beta4'编译'com.squareup.okhttp3:okhttp:3.1.2'

而我的Authenticator是这样的:

public class CustomAuthanticator 实现了 Authenticator {@覆盖公共请求身份验证(路由路由,响应响应)抛出 IOException {//刷新访问令牌refreshTokenResult=apiService.refreshUserToken(参数);//这是同步改造请求RefreshTokenResult refreshResult = refreshTokenResult.execute().body();//检查响应是否等于400,表示空响应if(refreshResult != null) {//保存新的访问和刷新令牌//然后创建一个新的请求和新的访问令牌作为标头返回 response.request().newBuilder().header(授权", newaccesstoken).建造();} 别的 {//我们得到了空响应,我们应该返回 null//如果我们不返回 null//此方法将尝试发出如此多的请求以获取新的访问令牌返回空;}}}

这是我的 APIService 类:

公共接口 APIService {@FormUrlEncoded@Headers(缓存控制:无缓存")@POST(令牌")公共调用refreshUserToken(@Header("Accept") 字符串接受,@Header("Content-Type") String contentType, @Field("grant_type") String grantType,@Field("client_id") String clientId, @Field("client_secret") String clientSecret,@Field("refresh_token") String refreshToken);}

我正在像这样使用 Retrofit:

CustomAuthanticator customAuthanticator=new CustomAuthanticator();OkHttpClient okClient = new OkHttpClient.Builder().authenticator(customAuthanticator).建造();改造客户端 = new Retrofit.Builder().baseUrl(getResources().getString(R.string.base_api_url)).addConverterFactory(GsonConverterFactory.create(gson)).client(okClient).建造();//然后提出改造请求

所以我的问题是:有时我会获得一个新的访问令牌并继续工作.但有时我会收到 400 响应,这意味着一个空响应.所以我的旧刷新令牌无效,我无法获得新令牌.通常我们的刷新令牌会在 1 年内到期.那么我该如何做到这一点.请帮帮我!

解决方案

免责声明:实际上我正在使用 Dagger +RxJava + Retrofit 但我只是想提供一个答案来为未来的访问者演示逻辑.

<块引用>

重要:如果您从多个地方发出请求,您的令牌将在 TokenAuthenticator 类中多次刷新.例如,当您的活动和服务同时发出请求时.要解决这个问题,只需将 synchronized 关键字添加到您的 TokenAuthenticatorauthenticate 方法.

<块引用>

请在 Authenticator 中刷新令牌时发出同步请求,因为您必须阻塞该线程直到请求完成,否则您的请求将使用旧令牌和新令牌执行两次.您可以在刷新令牌以阻止该线程时使用 Schedulers.trampoline()blockingGet().

<块引用>

同样在 authenticate 方法中,您可以通过比较请求令牌和存储的令牌来检查令牌是否已经刷新,以防止不必要的刷新.

<块引用>

并且请不要考虑使用TokenInterceptor,因为它是边缘情况,并不适合所有人,只需关注TokenAuthenticator.

这就是我们正在努力实现的目标:

首先刷新令牌是大多数应用程序的关键过程.流程是:如果刷新令牌失败,则注销当前用户并要求重新登录.(可能在注销用户之前重试刷新令牌几次)

反正我会一步一步解释:

第一步:请参考单例模式,我们将创建一个负责返回我们的改造实例的类.由于它是静态的,如果没有可用的实例,它只会创建一次实例,并且在您调用它时总是返回这个静态实例.这也是单例设计模式的基本定义.

公共类 RetrofitClient {私有静态改造改造 = null;私人改造客户端(){//防止访问的私有构造函数//唯一访问方式:Retrofit client = RetrofitClient.getInstance();}公共静态改造 getInstance() {如果(改造==空){//TokenAuthenticator 也可以是单例的TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();//!!这个拦截器不是每个人都需要的!!//这个拦截器的主要目的是减少服务器调用//我们的令牌需要在 10 小时后刷新//我们在 50 小时后打开我们的应用程序并尝试发出请求.//当然令牌已过期,我们将收到 401 响应.//所以这个拦截器会预先检查时间并刷新令牌.//如果失败并且我得到 401,那么我的 TokenAuthenticator 完成它的工作.//如果我的 TokenAuthenticator 也失败了,基本上我只是注销用户.TokenInterceptor tokenInterceptor = new TokenInterceptor();OkHttpClient okClient = new OkHttpClient.Builder().authenticator(tokenAuthenticator).addInterceptor(tokenInterceptor).建造();改造 = 新改造.Builder().baseUrl(base_api_url).client(okClient).建造();}返回改造;}}

第 2 步:在我的 TokenAuthenticator 的 authenticate 方法中:

@Override公共同步请求身份验证(路由路由,响应响应)抛出 IOException {boolean refreshResult = refreshToken();如果(刷新结果){//刷新令牌成功,我们将新令牌保存到存储中.//从存储中获取您的令牌并设置标头String newaccesstoken = "你的新访问令牌";//使用新的访问令牌再次执行失败的请求返回 response.request().newBuilder().header(授权", newaccesstoken).建造();} 别的 {//刷新令牌失败,您可以注销用户或重试几次//这里返回 null 很关键,它会停止当前的请求//如果你不返回null,你将进入一个循环调用refresh返回空;}}

还有 refreshToken 方法,这只是一个你可以创建自己的例子:

public boolean refreshToken() {//你可以在 Retrofit 中使用 RxJava 并添加 blocksGet//如何刷新令牌取决于您RefreshTokenResult 结果 = retrofit.refreshToken();int responseCode = result.getResponseCode();如果(响应代码 == 200){//将新令牌保存到 sharedpreferences、storage 等.返回真;} 别的 {//无法刷新返回假;}}

第3步:对于那些想看TokenInterceptor逻辑的人:

public class TokenInterceptor 实现 Interceptor {SharedPreferences 首选项;SharedPreferences.Editor prefsEdit;@覆盖公共响应拦截(链链)抛出 IOException {请求 newRequest = chain.request();//从共享首选项中获取过期时间long expireTime = prefs.getLong(expiretime",0);日历 c = Calendar.getInstance();日期 nowDate = c.getTime();c.setTimeInMillis(expireTime);日期 expireDate = c.getTime();int result = nowDate.compareTo(expireDate);//比较日期时 -1 表示日期已过,因此我们需要刷新令牌如果(结果 == -1){//在这里刷新令牌,并获得新的访问令牌TokenResponse tokenResponse = refreshToken();//保存刷新令牌的过期时间:整数 expiresIn = tokenResponse.getExpiresIn();日历 c = Calendar.getInstance();c.add(Calendar.SECOND,expiresIn);prefsEdit.putLong("expiretime",c.getTimeInMillis());String newaccessToken = "新访问令牌";newRequest=chain.request().newBuilder().header(授权", newaccessToken).建造();}返回 chain.proceed(newRequest);}}

我在活动和后台服务中提出请求.他们都使用相同的改造实例,我可以轻松管理访问令牌.请参考此答案并尝试创建您自己的客户端.如果您仍有问题,请在下方评论,我会尽力提供帮助.

I am using Retrofit and OkHttp libraries. I have an Authenticator that authenticates the user when we get a 401 response.

My build.gradle is like this:

compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.squareup.okhttp3:okhttp:3.1.2'

And my Authenticator is like this:

public class CustomAuthanticator  implements Authenticator {
@Override
public Request authenticate(Route route, Response response) throws IOException {
    
    //refresh access token
    refreshTokenResult=apiService.refreshUserToken(parameters);
    //this is synchronous retrofit request
    RefreshTokenResult refreshResult = refreshTokenResult.execute().body();
    //check if response equals 400, means empty response
    if(refreshResult != null) {
        // save new access and refresh token
        // then create a new request and new access token as header
        return response.request().newBuilder()
                .header("Authorization", newaccesstoken)
                .build();

    } else {
        // we got empty response and we should return null
        // if we don't return null
        // this method will try to make so many requests to get new access token
        return null;
    }
                    
}}

This is my APIService class :

public interface APIService {

@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST("token")
public Call<RefreshTokenResult> refreshUserToken(@Header("Accept") String accept, 
    @Header("Content-Type") String contentType, @Field("grant_type") String grantType,
    @Field("client_id") String clientId, @Field("client_secret") String clientSecret, 
    @Field("refresh_token") String refreshToken);
}

I am using Retrofit like this:

CustomAuthanticator customAuthanticator=new CustomAuthanticator();
OkHttpClient okClient = new OkHttpClient.Builder()
        .authenticator(customAuthanticator)
        .build();
Retrofit client = new Retrofit.Builder()
        .baseUrl(getResources().getString(R.string.base_api_url))
        .addConverterFactory(GsonConverterFactory.create(gson))
        .client(okClient)
        .build();
    
//then make retrofit request

So my question is: Sometimes I get a new access token and continue work. But sometimes I get a 400 response which means an empty response. So my old refresh token is invalid and I can't get a new token. Normally our refresh token expires in 1 year. So how I can do this. Please help me!

解决方案

Disclaimer : Actually I am using Dagger +RxJava + Retrofit but I just wanted to provide an answer to demonstrate logic for future visitors.

Important : If you are making requests from several places your token will refresh multiple times inside TokenAuthenticator class. For example when your activity and your service make requests concurrently. To beat this issue just add synchronized keyword to your TokenAuthenticators authenticate method.

Please make synchronous requests when refreshing your token inside Authenticator because you must block that thread until your request finishes, otherwise your requests will be executed twice with old and new tokens. You can use Schedulers.trampoline() or blockingGet() when refreshing your token to block that thread.

Also inside authenticate method you can check if token is already refreshed by comparing request token with stored token to prevent unnecessary refresh.

And please do not consider using TokenInterceptor because it is edge case and not for everyone, just focus on TokenAuthenticator.

This is what we are trying to achieve:

First of all refreshing token is a critical process for most apps. The flow is: If refresh token fails, logout current user and require to re-login. (Maybe retry refresh token couple of times before logging out the user)

Anyways I will explain it step by step:

Step 1: Please refer singleton pattern, we will create one class that's responsible for returning our retrofit instance. Since it is static if there is no instance available it just creates instance only once and when you call it always returns this static instance. This is also basic definition of Singleton design pattern.

public class RetrofitClient {

private static Retrofit retrofit = null;

private RetrofitClient() {
    // private constructor to prevent access
    // only way to access: Retrofit client = RetrofitClient.getInstance();
}

public static Retrofit getInstance() {
    if (retrofit == null) {
        // TokenAuthenticator can be singleton too
        TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();

        // !! This interceptor is not required for everyone !!
        // Main purpose of this interceptor is to reduce server calls

        // Our token needs to be refreshed after 10 hours
        // We open our app after 50 hours and try to make a request.
        // Of course token is expired and we will get a 401 response.
        // So this interceptor checks time and refreshes token beforehand.
        // If this fails and I get 401 then my TokenAuthenticator does its job.
        // if my TokenAuthenticator fails too, basically I just logout the user.
        TokenInterceptor tokenInterceptor = new TokenInterceptor();

        OkHttpClient okClient = new OkHttpClient.Builder()
                .authenticator(tokenAuthenticator)
                .addInterceptor(tokenInterceptor)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(base_api_url)
                .client(okClient)
                .build();
    }
    return retrofit;
  }
}

Step 2: In my TokenAuthenticator's authenticate method :

@Override
public synchronized Request authenticate(Route route, Response response) throws IOException {

    boolean refreshResult = refreshToken();
    if (refreshResult) {
    // refresh token is successful, we saved new token to storage.
    // Get your token from storage and set header
    String newaccesstoken = "your new access token";

    // execute failed request again with new access token
    return response.request().newBuilder()
            .header("Authorization", newaccesstoken)
            .build();

    } else {
        // Refresh token failed, you can logout user or retry couple of times
        // Returning null is critical here, it will stop the current request
        // If you do not return null, you will end up in a loop calling refresh
        return null;
    }
}

And refreshToken method, this is just an example you can create your own:

public boolean refreshToken() {
    // you can use RxJava with Retrofit and add blockingGet
    // it is up to you how to refresh your token
    RefreshTokenResult result = retrofit.refreshToken();
    int responseCode = result.getResponseCode();

    if(responseCode == 200) {
        // save new token to sharedpreferences, storage etc.
        return true;
    } else {
        //cannot refresh
        return false;
    } 
}

Step 3: For those who wants to see TokenInterceptor logic:

public class TokenInterceptor implements Interceptor {
SharedPreferences prefs;
SharedPreferences.Editor prefsEdit;

@Override
public Response intercept(Chain chain) throws IOException {

    Request newRequest = chain.request();

    // get expire time from shared preferences
    long expireTime = prefs.getLong("expiretime",0);
    Calendar c = Calendar.getInstance();
    Date nowDate = c.getTime();
    c.setTimeInMillis(expireTime);
    Date expireDate = c.getTime();

    int result = nowDate.compareTo(expireDate);
    // when comparing dates -1 means date passed so we need to refresh token
    if(result == -1) {
        //refresh token here , and get new access token
        TokenResponse tokenResponse = refreshToken();

        // Save refreshed token's expire time :
        integer expiresIn = tokenResponse.getExpiresIn();
        Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND,expiresIn);
        prefsEdit.putLong("expiretime",c.getTimeInMillis());

        String newaccessToken = "new access token";
        newRequest=chain.request().newBuilder()
                .header("Authorization", newaccessToken)
                .build();
    }
    return chain.proceed(newRequest);
  }
}

I am making requests at activities and background services. All of them uses the same retrofit instance and I can easily manage access token. Please refer to this answer and try to create your own client. If you still have issues simply comment below, I'll try to help.

这篇关于Android Retrofit2 刷新 Oauth 2 令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆