Android Retrofit2刷新Oauth 2令牌

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

我的build.gradle是这样的:

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

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

  1. import java.io.IOException;
  2. import okhttp3.Authenticator;
  3. import okhttp3.Request;
  4. import okhttp3.Response;
  5. import okhttp3.Route;
  6.  
  7. public class CustomAuthanticator implements Authenticator {
  8. @Override
  9. public Request authenticate(Route route,Response response) throws IOException {
  10.  
  11. //refresh access token via refreshtoken
  12.  
  13. Retrofit client = new Retrofit.Builder()
  14. .baseUrl(baseurl)
  15. .addConverterFactory(GsonConverterFactory.create())
  16. .build();
  17. APIService service = client.create(APIService.class);
  18. Call<RefreshTokenResult> refreshTokenResult=service.refreshUserToken("application/json","application/json","refresh_token",client_id,client_secret,refresh_token);
  19. //this is syncronous retrofit request
  20. RefreshTokenResult refreshResult= refreshTokenResult.execute().body();
  21. //check if response equals 400,mean empty response
  22. if(refreshResult!=null) {
  23. //save new access and refresh token
  24. // than create a new request and modify it accordingly using the new token
  25. return response.request().newBuilder()
  26. .header("Authorization",newaccesstoken)
  27. .build();
  28.  
  29. } else {
  30. //we got empty response and return null
  31. //if we dont return null this method is trying to make so many request
  32. //to get new access token
  33. return null;
  34.  
  35. }
  36.  
  37. }}

这是我的APIService类:

  1. import retrofit2.Call;
  2. import retrofit2.http.Body;
  3. import retrofit2.http.Field;
  4. import retrofit2.http.FormUrlEncoded;
  5. import retrofit2.http.GET;
  6. import retrofit2.http.Header;
  7. import retrofit2.http.Headers;
  8. import retrofit2.http.POST;
  9. import retrofit2.http.Query;
  10.  
  11.  
  12. public interface APIService {
  13.  
  14.  
  15. @FormUrlEncoded
  16. @Headers("Cache-Control: no-cache")
  17. @POST("token")
  18. 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);
  19. }

我正在使用这样的说明:

  1. CustomAuthanticator customAuthanticator=new CustomAuthanticator();
  2. OkHttpClient okClient = new OkHttpClient.Builder()
  3. .authenticator(customAuthanticator)
  4. .build();
  5. Gson gson = new GsonBuilder()
  6. .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
  7. .create();
  8. Retrofit client = new Retrofit.Builder()
  9. .baseUrl(getResources().getString(R.string.base_api_url))
  10. .addConverterFactory(GsonConverterFactory.create(gson))
  11. .client(okClient)
  12. .build();
  13.  
  14. //then make retrofit request

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

解决方法

@Edit 07.04.2017:

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

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

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

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

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

  1. public class RetrofitClient {
  2.  
  3. private static Retrofit retrofit = null;
  4.  
  5. private RetrofitClient() {
  6. // this default constructor is private and you can't call it like :
  7. // RetrofitClient client = new RetrofitClient();
  8. // only way calling it : Retrofit client = RetrofitClient.getInstance();
  9. }
  10.  
  11. public static Retrofit getInstance() {
  12. if (retrofit == null) {
  13. // my token authenticator,I will add this class below to show the logic
  14. TokenAuthenticator tokenAuthenticator = new TokenAuthenticator();
  15.  
  16. // I am also using interceptor which controls token if expired
  17. // lets look at this scenerio : if my token needs to refresh after 10 hours but I came
  18. // to application after 50 hours and tried to make request.
  19. // ofc my token is invalid and if I make request it will return 401
  20. // so this interceptor checks time and refresh token immediately before making request and after makes current request
  21. // with refreshed token. So I do not get 401 response. But if this fails and I get 401 then my TokenAuthenticator do his job.
  22. // if my TokenAuthenticator fails too,basically I just logout user and tell him to relogin.
  23. TokenInterceptor tokenInterceptor = new TokenInterceptor();
  24.  
  25.  
  26. // this is the critical point that helped me a lot.
  27. // we using only one retrofit instance in our application
  28. // and it uses this dispatcher which can only do 1 request at the same time
  29.  
  30. // the docs says : Set the maximum number of requests to execute concurrently.
  31. // Above this requests queue in memory,waiting for the running calls to complete.
  32.  
  33. Dispatcher dispatcher = new Dispatcher();
  34. dispatcher.setMaxRequests(1);
  35.  
  36. // we using this OkHttp,you can add authenticator,interceptors,dispatchers,// logging stuff etc. easily for all your requests just editing this OkHttp
  37. OkHttpClient okClient = new OkHttpClient.Builder()
  38. .connectTimeout(Constants.CONNECT_TIMEOUT,TimeUnit.SECONDS)
  39. .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
  40. .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
  41. .authenticator(tokenAuthenticator)
  42. .addInterceptor(tokenInterceptor)
  43. .dispatcher(dispatcher)
  44. .build();
  45.  
  46. retrofit = new Retrofit.Builder()
  47. .baseUrl(context.getResources().getString(R.string.base_api_url))
  48. .addConverterFactory(GsonConverterFactory.create(new Gson()))
  49. .client(okClient)
  50. .build();
  51. }
  52.  
  53.  
  54. return retrofit;
  55. }
  56.  
  57. }

步骤2:在我的TokenAuthenticator的验证方法中:

  1. @Override
  2. public Request authenticate(Route route,Response response) throws IOException {
  3. String userRefreshToken="your refresh token";
  4. String cid="your client id";
  5. String csecret="your client secret";
  6. String baseUrl="your base url";
  7.  
  8. refreshResult=refreshToken(baseUrl,userRefreshToken,cid,csecret);
  9. if (refreshResult) {
  10. //refresh is successful
  11. String newaccess="your new access token";
  12.  
  13. // make current request with new access token
  14. return response.request().newBuilder()
  15. .header("Authorization",newaccess)
  16. .build();
  17.  
  18. } else {
  19. // refresh Failed,maybe you can logout user
  20. // returning null is critical here,because if you do not return null
  21. // it will try to refresh token continuously like 1000 times.
  22. // also you can try 2-3-4 times by depending you before logging out your user
  23. return null;
  24. }
  25. }

和refreshToken方法,这只是一个例子,您可以在刷新令牌时创建自己的策略,但我使用的是最简单的连接方法HttpUrlConnection.没有缓存,没有重试只尝试连接并获得结果:

  1. public boolean refreshToken(String url,String refresh,String cid,String csecret) throws IOException{
  2. URL refreshUrl=new URL(url+"token");
  3. HttpURLConnection urlConnection = (HttpURLConnection) refreshUrl.openConnection();
  4. urlConnection.setDoInput(true);
  5. urlConnection.setRequestMethod("POST");
  6. urlConnection.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
  7. urlConnection.setUseCaches(false);
  8. String urlParameters = "grant_type=refresh_token&client_id="+cid+"&client_secret="+csecret+"&refresh_token="+refresh;
  9.  
  10. urlConnection.setDoOutput(true);
  11. DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
  12. wr.writeBytes(urlParameters);
  13. wr.flush();
  14. wr.close();
  15.  
  16. int responseCode = urlConnection.getResponseCode();
  17.  
  18. if(responseCode==200){
  19. BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
  20. String inputLine;
  21. StringBuffer response = new StringBuffer();
  22.  
  23. while ((inputLine = in.readLine()) != null) {
  24. response.append(inputLine);
  25. }
  26. in.close();
  27.  
  28. // this gson part is optional,you can read response directly from Json too
  29. Gson gson = new Gson();
  30. RefreshTokenResult refreshTokenResult=gson.fromJson(response.toString(),RefreshTokenResult.class);
  31.  
  32. // handle new token ...
  33. // save it to the sharedpreferences,storage bla bla ...
  34. return true;
  35.  
  36. } else {
  37. //cannot refresh
  38. return false;
  39. }
  40.  
  41. }

步骤3:其实我们做了,但我会显示简单的用法

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

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

  1. public class TokenInterceptor implements Interceptor{
  2. Context ctx;
  3. SharedPreferences mPrefs;
  4. SharedPreferences.Editor mPrefsEdit;
  5.  
  6. public TokenInterceptor(Context ctx) {
  7. this.ctx = ctx;
  8. this.mPrefs= PreferenceManager.getDefaultSharedPreferences(ctx);
  9. mPrefsEdit=mPrefs.edit();
  10. }
  11.  
  12. @Override
  13. public Response intercept(Chain chain) throws IOException {
  14.  
  15. Request newRequest=chain.request();
  16.  
  17. //when saving expire time :
  18. integer expiresIn=response.getExpiresIn();
  19. Calendar c = Calendar.getInstance();
  20. c.add(Calendar.SECOND,expiresIn);
  21. mPrefsEdit.putLong("expiretime",c.getTimeInMillis());
  22.  
  23. //get expire time from shared preferences
  24. long expireTime=mPrefs.getLong("expiretime",0);
  25. Calendar c = Calendar.getInstance();
  26. Date nowDate=c.getTime();
  27. c.setTimeInMillis(expireTime);
  28. Date expireDate=c.getTime();
  29.  
  30. int result=nowDate.compareTo(expireDate);
  31. /**
  32. * when comparing dates -1 means date passed so we need to refresh token
  33. * see {@link Date#compareTo}
  34. */
  35. if(result==-1) {
  36. //refresh token here,and got new access token
  37. String newaccessToken="newaccess";
  38. newRequest=chain.request().newBuilder()
  39. .header("Authorization",newaccessToken)
  40. .build();
  41. }
  42. return chain.proceed(newRequest);
  43. }
  44. }

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

猜你在找的Android相关文章