Android Retrofit2刷新Oauth 2令牌

前端之家收集整理的这篇文章主要介绍了Android Retrofit2刷新Oauth 2令牌前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我正在使用Retrofit和OkHttp库.
所以我有Authenticator哪个authantate用户如果得到401响应.

我的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'

而我的自定义验证器在这里:

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);
}

我正在使用这样的说明:

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年后到期.那么我该怎么做呢请帮帮我 !

解决方法

@Edit 07.04.2017:

我更新了这个答案,因为它有点旧,我的情况改变了 – 我知道我有一个后台服务,

首先刷新令牌过程是关键过程.在我的应用程序和大多数应用程序中执行此操作:如果刷新令牌失败,注销当前用户并警告用户登录(也许您可以根据您重新启动刷新令牌进程2-3-4次)

@Important注意:请在Authenticator或Interceptor中刷新令牌时进行同步请求,因为您必须阻止该线程,直到您的请求完成,否则您的请求将使用旧的和新的令牌执行两次.

无论如何我会一步一步的解释一下:

步骤1:请参考singleton pattern,我们将创建一个类,负责返回我们的改装实例,无论何时我们想访问.由于它的静态,如果没有可用的实例,它只创建一个实例,当你调用它总是返回这个静态实例.这也是Singleton设计模式的基本定义.

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 calling it : Retrofit client = RetrofitClient.getInstance();
}

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

        // I am also using interceptor which controls token if expired
        // lets look at this scenerio : if my token needs to refresh after 10 hours but I came
        // to application after 50 hours and tried to make request.
        // ofc my token is invalid and if I make request it will return 401
        // so this interceptor checks time and refresh token immediately before making request and after makes current request
        // with refreshed token. So I do not get 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 relogin.
        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 using this OkHttp,you can add authenticator,interceptors,dispatchers,// logging stuff etc. easily for all your requests just editing this OkHttp
        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的验证方法中:

@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.没有缓存,没有重试只尝试连接并获得结果:

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:其实我们做了,但我会显示简单的用法

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

步骤4:对于那些想要看到TokenInterceptor逻辑的人:

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 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);
  }
}

在我的应用程序中,我正在应用程序和后台服务提出请求.他们都使用相同的实例,我可以轻松管理.请参考此答案,并尝试创建您自己的客户端.如果您仍然有以下问题,请提及我 – 另一个问题 – 或发送邮件.我有时间会帮助我希望这可以帮助.

原文链接:https://www.f2er.com/android/311264.html

猜你在找的Android相关文章