Yas*_*maz 26 android oauth-2.0 okhttp retrofit2 okhttp3
我正在使用Retrofit和OkHttp库.所以Authenticator如果获得401响应,我有哪个authanticate用户.
我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'
Run Code Online (Sandbox Code Playgroud)
我的习惯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;
}
}}
Run Code Online (Sandbox Code Playgroud)
这是我的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);
}
Run Code Online (Sandbox Code Playgroud)
我正在使用这样的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
Run Code Online (Sandbox Code Playgroud)
所以我的问题是:有时我会获得新的访问令牌并继续工作,提出新请求.但有时我得到400响应,这意味着空响应.所以我的旧刷新令牌无效,我无法获得新令牌.通常我们的刷新令牌将在1年后到期.所以我怎么能这样做.请帮我 !
Yas*_*maz 34
免责声明:其实我正在使用
Dagger+RxJava+RxAndroid+Retrofit但我只想提供一个答案来展示未来访客的逻辑.Schedulers.trampoline()刷新令牌以阻止该线程时,仅使用差异.如果您对这些库有更多疑问,请在下面评论,以便我可以提供其他答案或帮助您.
也
重要请阅读:如果您同时发出请求,但使用
dispatcher.setMaxRequests(1);您的令牌将在TokenInterceptor课堂内多次刷新.例如,当您的应用和服务同时发出请求时.要解决此问题,只需synchronized在您的intercept方法中添加关键字TokenInterceptor:public synchronized Response intercept(Chain chain)
@Edit 07.04.2017:
我更新了这个答案,因为它有点旧,我的情况发生了变化 - 现在我有一个后台服务,它也提出了要求 -
首先,刷新令牌过程是关键过程.在我的应用程序和大多数应用程序执行此操作:如果刷新令牌失败注销当前用户并警告用户登录.(也许您可以根据您重试刷新令牌过程2-3-4次)
@Important注意:请在刷新令牌时发出同步请求,Authenticator或者Interceptor因为您必须阻止该线程,直到您的请求完成,否则您的请求将使用旧令牌和新令牌执行两次.
无论如何,我将逐步解释它:
第1步:请参考单例模式,我们将创建一个类,负责随时随地返回我们的改造实例.由于它是静态的,如果没有可用的实例,它只创建一次实例,当你调用它时总是返回这个静态实例.这也是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 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;
}
}
Run Code Online (Sandbox Code Playgroud)
第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;
}
}
Run Code Online (Sandbox Code Playgroud)
和refreshToken方法,这只是一个示例,您可以在刷新令牌时创建自己的策略.我正在使用,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;
}
}
Run Code Online (Sandbox Code Playgroud)
第3步:实际上我们制作了它,但我将展示简单的用法:
Retrofit client= RetrofitClient.getInstance();
//interface for requests
APIService service = client.create(APIService.class);
// then do your requests .....
Run Code Online (Sandbox Code Playgroud)
第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 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);
}
}
Run Code Online (Sandbox Code Playgroud)
在我的应用程序中,我正在申请和后台服务.他们两个使用相同的实例,我可以轻松管理.请参考此答案并尝试创建自己的客户端.如果你仍然有问题只需在下面评论,请提及我 - 即使是另一个问题 - 或发送邮件.我有空的时候会帮忙的.希望这可以帮助.
| 归档时间: |
|
| 查看次数: |
19349 次 |
| 最近记录: |