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

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

问题描述

我正在使用RetrofitOkHttp库. 因此,我有Authenticator,它会在获得401响应时对用户进行身份验证.

I am using Retrofit and OkHttp libraries. So I have Authenticator which authanticate user if gets 401 response.

我的build.gradle就像这样:

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'

我的自定义Authenticator在这里:

import java.io.IOException;
import okhttp3.Authenticator;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;

public class CustomAuthanticator  implements Authenticator {
@Override
public Request authenticate(Route route, Response response) throws IOException {

    //refresh access token via refreshtoken

    Retrofit client = new Retrofit.Builder()
            .baseUrl(baseurl)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    APIService service = client.create(APIService.class);
    Call<RefreshTokenResult> refreshTokenResult=service.refreshUserToken("application/json", "application/json", "refresh_token",client_id,client_secret,refresh_token);
    //this is syncronous retrofit request
    RefreshTokenResult refreshResult= refreshTokenResult.execute().body();
    //check if response equals 400 , mean empty response
    if(refreshResult!=null) {
       //save new access and refresh token
        // than create a new request and modify it accordingly using the new token
        return response.request().newBuilder()
                .header("Authorization", newaccesstoken)
                .build();

    } else {
        //we got empty response and return null
        //if we dont return null this method is trying to make so many request
        //to get new access token
        return null;

    }

}}

这是我的APIService课:

import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.Query;


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 authanticator like that :

CustomAuthanticator customAuthanticator=new CustomAuthanticator();
OkHttpClient okClient = new OkHttpClient.Builder()
        .authenticator(customAuthanticator)
        .build();
Gson gson = new GsonBuilder()
        .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
        .create();
Retrofit client = new Retrofit.Builder()
        .baseUrl(getResources().getString(R.string.base_api_url))
        .addConverterFactory(GsonConverterFactory.create(gson))
        .client(okClient)
        .build();

//then make retrofit request

所以我的问题是:有时我会获得新的访问令牌并继续工作,提出新的请求.但是有时候我会得到400响应,这意味着响应是空的.因此我的旧刷新令牌无效,并且我无法获得新令牌.通常,我们的刷新令牌会在1年后过期.所以我该怎么做.请帮助我!

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

推荐答案

免责声明: 实际上,我正在使用Dagger + RxJava + RxAndroid + Retrofit,但是我只是想提供一个答案,以向未来的访问者展示逻辑. 唯一的区别是在刷新令牌以阻止该线程时使用Schedulers.trampoline().如果您对这些库还有其他疑问,请在下面评论,以便我可以提供其他答案或为您提供帮助.

Disclaimer : Actually I am using Dagger +RxJava + RxAndroid + Retrofit but I just wanted to provide an answer to demonstrate logic for future visitors. Only difference is use Schedulers.trampoline() when refreshing your token to block that thread. If you have more questions about these libraries please comment below so maybe I can provide another answer or help you.

重要信息,请阅读以下内容: 如果您要同时进行请求,但也使用dispatcher.setMaxRequests(1);,则令牌将在TokenInterceptor类中多次刷新.例如,当您的应用程序和服务同时发出请求时.要解决此问题,只需在TokenInterceptor中的intercept方法中添加synchronized关键字:public synchronized Response intercept(Chain chain)

Important Please Read This : If you are making requests concurrently but also using dispatcher.setMaxRequests(1); your token will refresh multiple times inside TokenInterceptor class. For example when your app and your service makes requests concurrently. For beating this issue just add synchronized keyword to your intercept method inside TokenInterceptor: public synchronized Response intercept(Chain chain)

@Edit 07.04.2017:

我更新了此答案,因为它有点旧并且我的情况有所变化-现在我有一个后台服务也可以发出请求-

I updated this answer because it was a bit old and my circumstances changed -now I have a background service which makes requests too-

首先,刷新令牌过程是关键过程.在我的应用程序和大多数应用程序中,这样做:如果刷新令牌失败,请注销当前用户并警告用户登录.(也许您可以根据自己的情况重试刷新令牌过程2-3-4次)

First of all the refresh token process is critical process. In my application and most of applications doing this : If refresh token fails logout current user and warn user to login .(Maybe you can retry refresh token process by 2-3-4 times depending on you)

@重要通知:在刷新AuthenticatorInterceptor内的令牌时,请发出同步请求,因为您必须阻塞该线程,直到请求完成,否则您的请求将被旧的和新的执行两次令牌.

@Important Notice: Please make synchronous requests when refreshing your token inside Authenticator or Interceptor because of you must block that thread until your request finish otherwise your requests executed twice with old and new token.

无论如何,我将逐步解释它:

Anyways I will explain it step by step :

第1步::请参考单人模式,将创建一个类,该类负责随时随地访问我们的改造实例.由于它是静态的(如果没有可用的实例),因此仅创建一次实例,并且在调用它时始终返回此静态实例.这也是Singleton设计模式的基本定义.

Step 1 : Please refer singleton pattern, we will create one class thats responsible for returning our retrofit instance whenever, wherever we want to access. Since its 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() {
    // this default constructor is private and you can't call it like :
    // RetrofitClient client = new RetrofitClient();
    // only way to get it : Retrofit client = RetrofitClient.getInstance();
}

public static Retrofit getInstance() {
    if (retrofit == null) {
        // my token authenticator, I will add this class at below
        TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();

        // I am also using interceptor which controls token if expired
        // lets look at this scenario : My token needs to refresh after 10 hours
        // but I came to application after 50 hours and tried to make request.
        // of course my token is invalid and it will return 401
        // so this interceptor checks time and refreshes token immediately before making request 
        // then continues request with refreshed token
        // So I do not get any 401 response. But if this fails and I get 401 then my TokenAuthenticator do his job.
        // if my TokenAuthenticator fails too, basically I just logout user and tell him to re-login.
        TokenInterceptor tokenInterceptor = new TokenInterceptor();

        // this is the critical point that helped me a lot.
        // we using only one retrofit instance in our application
        // and it uses this dispatcher which can only do 1 request at the same time

        // the docs says : Set the maximum number of requests to execute concurrently.
        // Above this requests queue in memory, waiting for the running calls to complete.

        Dispatcher dispatcher = new Dispatcher();
        dispatcher.setMaxRequests(1);

        // we are using this OkHttp as client, you can add authenticator, interceptors, dispatchers,
        // logging etc. easily for all your requests just editing this OkHttp client
        OkHttpClient okClient = new OkHttpClient.Builder()
                .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
                .readTimeout(Constants.READ_TIMEOUT, TimeUnit.SECONDS)
                .writeTimeout(Constants.WRITE_TIMEOUT, TimeUnit.SECONDS)
                .authenticator(tokenAuthenticator)
                .addInterceptor(tokenInterceptor)
                .dispatcher(dispatcher)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(context.getResources().getString(R.string.base_api_url))
                .addConverterFactory(GsonConverterFactory.create(new Gson()))
                .client(okClient)
                .build();
    }
    return retrofit;
}

}

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

Step 2 : In my TokenAuthenticator's authenticate method :

@Override
public Request authenticate(Route route, Response response) throws IOException {
    String userRefreshToken="your refresh token";
    String cid="your client id";
    String csecret="your client secret";
    String baseUrl="your base url";

    refreshResult=refreshToken(baseUrl,userRefreshToken,cid,csecret);
    if (refreshResult) {
    //refresh is successful
    String newaccess="your new access token";

    // make current request with new access token
    return response.request().newBuilder()
            .header("Authorization", newaccess)
            .build();

    } else {
        // refresh failed , maybe you can logout user
        // returning null is critical here, because if you do not return null 
        // it will try to refresh token continuously like 1000 times.
        // also you can try 2-3-4 times by depending you before logging out your user
        return null;
    }
}

refreshToken方法,这只是示例,您可以在刷新令牌时创建自己的策略.我正在使用HttpUrlConnection,因为刷新令牌时会遇到其他情况.同时,我鼓励您使用Retrofit.无论如何:

and refreshToken method , this is just example you can create your own strategy when refreshing your token. I am using HttpUrlConnection because I have extra circumstances when refreshing my token. In the meantime I encourage you to use Retrofit. Anyways:

public boolean refreshToken(String url,String refresh,String cid,String csecret) throws IOException{
    URL refreshUrl=new URL(url+"token");
    HttpURLConnection urlConnection = (HttpURLConnection) refreshUrl.openConnection();
    urlConnection.setDoInput(true);
    urlConnection.setRequestMethod("POST");
    urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
    urlConnection.setUseCaches(false);
    String urlParameters  = "grant_type=refresh_token&client_id="+cid+"&client_secret="+csecret+"&refresh_token="+refresh;

    urlConnection.setDoOutput(true);
    DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
    wr.writeBytes(urlParameters);
    wr.flush();
    wr.close();

    int responseCode = urlConnection.getResponseCode();

    if(responseCode==200){
        BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();

        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();

        // this gson part is optional , you can read response directly from Json too
        Gson gson = new Gson();
        RefreshTokenResult refreshTokenResult=gson.fromJson(response.toString(),RefreshTokenResult.class);

        // handle new token ...
        // save it to the sharedpreferences, storage bla bla ...
        return true;

    } else {
        //cannot refresh
        return false;
    } 

}

第3步:实际上,我们做到了,但是我将展示简单的用法:

Step 3 : Actually we made it but I will show simple usage :

Retrofit client= RetrofitClient.getInstance();
//interface for requests
APIService service = client.create(APIService.class);
// then do your requests .....

第4步::对于那些想了解TokenInterceptor逻辑的人:

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

public class TokenInterceptor implements Interceptor{
Context ctx;
SharedPreferences mPrefs;
SharedPreferences.Editor mPrefsEdit;

public TokenInterceptor(Context ctx) {
    this.ctx = ctx;
    this.mPrefs= PreferenceManager.getDefaultSharedPreferences(ctx);
    mPrefsEdit=mPrefs.edit();
}

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

    Request newRequest=chain.request();

    //when saving expire time :
    integer expiresIn=response.getExpiresIn();
    Calendar c = Calendar.getInstance();
    c.add(Calendar.SECOND,expiresIn);
    mPrefsEdit.putLong("expiretime",c.getTimeInMillis());

    //get expire time from shared preferences
    long expireTime=mPrefs.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
     * see {@link Date#compareTo}
     */
    if(result==-1) {
        //refresh token here , and got new access token
        String newaccessToken="newaccess";
        newRequest=chain.request().newBuilder()
                .header("Authorization", newaccessToken)
                .build();
    }
    return chain.proceed(newRequest);
  }
}

在我的应用程序中,我正在应用程序和后台服务中发出请求.他们两个都使用相同的实例,因此我可以轻松地进行管理.请参考此答案并尝试创建自己的客户端.如果您仍然有问题,请在下面简单评论,甚至提及我-甚至是其他问题-或发送邮件.有空的时候我会帮忙的.希望这会有所帮助.

In my application I am making requests at application and background service. Both of them using same instance and I can easily manage. Please refer this answer and try to create your own client. If you still have issues simply comment below, mention me -even another question- or send mail. I will help when I have time. Hope this helps.

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

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