所以我有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年后到期.那么我该怎么做呢请帮帮我 !
解决方法@H_502_23@
@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;
}
}
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);
}
}
在我的应用程序中,我正在应用程序和后台服务提出请求.他们都使用相同的实例,我可以轻松管理.请参考此答案,并尝试创建您自己的客户端.如果您仍然有以下问题,请提及我 – 另一个问题 – 或发送邮件.我有时间会帮助我希望这可以帮助.
我更新了这个答案,因为它有点旧,我的情况改变了 – 我知道我有一个后台服务,
首先刷新令牌过程是关键过程.在我的应用程序和大多数应用程序中执行此操作:如果刷新令牌失败,注销当前用户并警告用户登录(也许您可以根据您重新启动刷新令牌进程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; } }
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); } }
在我的应用程序中,我正在应用程序和后台服务提出请求.他们都使用相同的实例,我可以轻松管理.请参考此答案,并尝试创建您自己的客户端.如果您仍然有以下问题,请提及我 – 另一个问题 – 或发送邮件.我有时间会帮助我希望这可以帮助.