Android Retrofit2刷新Oauth 2令牌

我正在使用RetrofitOkHttp库。 所以我有Authenticator用户如果得到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' 

而我的自定义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); } 

我正在使用authanticator这样的:

 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年内到期。 所以我怎么能做到这一点。 请帮帮我 !

Solutions Collecting From Web of "Android Retrofit2刷新Oauth 2令牌"

免责声明 :其实我正在使用Dagger + RxJava + RxAndroid + Retrofit但我只是想提供一个答案来展示未来访问者的逻辑。 唯一不同的是使用Schedulers.trampoline()时刷新您的令牌阻止该线程。 如果您对这些图书馆有更多的疑问,请在下面留言,这样我可以提供其他答案或帮助您。

重要请阅读 :如果您同时发出请求,但也使用dispatcher.setMaxRequests(1); 您的令牌将在TokenInterceptor类中多次刷新。 例如,您的应用程序提出请求,但同时您的服务也提出请求。 为了解决这个问题,只需要在TokenInterceptor里添加synchronized关键字到你的intercept方法中: public **synchronized** Response intercept(Chain chain)

在这里输入图像说明

@编辑07.04.2017:

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

首先刷新令牌进程是关键的过程。 在我的应用程序和大部分应用程序中这样做:如果刷新令牌未能注销当前用户并警告用户login(也许您可以重新刷新令牌处理2-3-4次,具体取决于您)

@Important注意 :请在AuthenticatorInterceptor刷新令牌时发出同步请求,因为您必须阻止该线程直到请求完成,否则请求会使用旧令牌和新令牌执行两次。

无论如何,我会一步一步解释:

第一步:请参阅单例模式 ,我们将创build一个类,负责无论何时何地访问我们的改造实例。 由于它的静态,如果没有实例可用它只是创build一次实例,当你调用它总是返回这个静态实例。 这也是Singletondevise模式的基本定义。

 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的authenticate方法:

 @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方法,这只是一个例子,您可以创build自己的策略刷新您的令牌。 我正在使用HttpUrlConnection因为我刷新我的令牌时有额外的情况。 同时,我鼓励你使用Retrofit 。 无论如何:

 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 ..... 

第四步:对于那些想看到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 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); } } 

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

在你的ApiClient.java类中:

 OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new AuthorizationInterceptor(context)) .build(); 

在你的改进包中添加TokenManager.java

 package co.abc.retrofit; /** * Created by ravindrashekhawat on 17/03/17. */ public interface TokenManager { String getToken(); boolean hasToken(); void clearToken(); String refreshToken(); } 

在名称为AuthorizationInterceptor.java的包中添加Intercepter类

 package co.smsmagic.retrofit; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.util.Log; import com.google.gson.Gson; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import co.abc.models.RefreshTokenResponseModel; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Retrofit; import retrofit2.http.Header; import static co.abc.utils.abcConstants.ACCESS_TOKEN; import static co.abc.utils.abcConstants.BASE_URL; import static co.abc.utils.abcConstants.GCM_TOKEN; import static co.abc.utils.abcConstants.JWT_TOKEN_PREFIX; import static co.abc.utils.abcConstants.REFRESH_TOKEN; /** * Created by ravindrashekhawat on 21/03/17. */ public class AuthorizationInterceptor implements Interceptor { private static Retrofit retrofit = null; private static String deviceToken; private static String accessToken; private static String refreshToken; private static TokenManager tokenManager; private static Context mContext; public AuthorizationInterceptor(Context context) { this.mContext = context; } @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Request modifiedRequest = null; tokenManager = new TokenManager() { final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); @Override public String getToken() { accessToken = sharedPreferences.getString(ACCESS_TOKEN, ""); return accessToken; } @Override public boolean hasToken() { accessToken = sharedPreferences.getString(ACCESS_TOKEN, ""); if (accessToken != null && !accessToken.equals("")) { return true; } return false; } @Override public void clearToken() { sharedPreferences.edit().putString(ACCESS_TOKEN, "").apply(); } @Override public String refreshToken() { final String accessToken = null; RequestBody reqbody = RequestBody.create(null, new byte[0]); OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(BASE_URL + "refresh") .method("POST", reqbody) .addHeader("Authorization", JWT_TOKEN_PREFIX + refreshToken) .build(); try { Response response = client.newCall(request).execute(); if ((response.code()) == 200) { // Get response String jsonData = response.body().string(); Gson gson = new Gson(); RefreshTokenResponseModel refreshTokenResponseModel = gson.fromJson(jsonData, RefreshTokenResponseModel.class); if (refreshTokenResponseModel.getRespCode().equals("1")) { sharedPreferences.edit().putString(ACCESS_TOKEN, refreshTokenResponseModel.getResponse()).apply(); return refreshTokenResponseModel.getResponse(); } } } catch (IOException e) { e.printStackTrace(); } return accessToken; } }; final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); deviceToken = sharedPreferences.getString(GCM_TOKEN, ""); accessToken = sharedPreferences.getString(ACCESS_TOKEN, ""); refreshToken = sharedPreferences.getString(REFRESH_TOKEN, ""); Response response = chain.proceed(request); boolean unauthorized =false; if(response.code() == 401 || response.code() == 422){ unauthorized=true; } if (unauthorized) { tokenManager.clearToken(); tokenManager.refreshToken(); accessToken = sharedPreferences.getString(ACCESS_TOKEN, ""); if(accessToken!=null){ modifiedRequest = request.newBuilder() .addHeader("Authorization", JWT_TOKEN_PREFIX + tokenManager.getToken()) .build(); return chain.proceed(modifiedRequest); } } return response; } } 

注意:这是刷新令牌的工作代码,我已经提供了保持冷静,只是改变一些常数,只是它会完美地工作。只要尝试理解逻辑。

在底部有逻辑再次调用相同的请求

  if(accessToken!=null){ modifiedRequest = request.newBuilder() .addHeader("Authorization", JWT_TOKEN_PREFIX + tokenManager.getToken()) .build(); return chain.proceed(modifiedRequest); }