免責聲明: 其實我使用Dagger
+ RxJava
+ RxAndroid
+ Retrofit
,但我只是想提供一個答案,以展示對未來遊客的邏輯。 刷新令牌以阻止該線程時,唯一區別是使用Schedulers.trampoline()
。如果您對這些圖書館有更多疑問,請在下面評論,以便我可以提供其他答案或幫助您。
而且
重要請仔細閱讀本: 如果你同時發出請求,而且使用dispatcher.setMaxRequests(1);
您的令牌將刷新內部TokenInterceptor
類多次。例如,當您的應用和您的服務同時發出請求時。爲了擊敗這個問題只需要添加關鍵字到intercept
方法內TokenInterceptor
:public synchronized Response intercept(Chain chain)
@Edit 2017年4月7日:
我這個答案進行更新,因爲這是一個有點老我的情況改變了 - 現在我有一個後臺服務,它也提出了請求 -
首先是refre sh令牌進程是關鍵進程。在我的應用程序和大多數應用程序中這樣做:如果刷新令牌未能註銷當前用戶並警告用戶登錄。(也許你可以根據你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 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;
}
}
步驟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
方法,這僅僅是例子,你可以刷新你的令牌時創建自己的策略。我正在使用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 .....
第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);
}
}
在我的應用程序中,我在應用程序和後臺服務中提出請求。他們都使用相同的實例,我可以輕鬆管理。請參考這個答案,並嘗試創建自己的客戶端。如果您仍然有問題,請在下面評論,請提及我 - 甚至是另一個問題 - 或發送郵件。有空的時候我會幫忙的。希望這可以幫助。
很好的回答。我在哪裏可以找到'TokenInterceptor'類? –
親愛的@IbrahimDisouki我爲TokenInterceptor邏輯添加了一個步驟。請結帳,讓我知道你是否有問題。 –
感謝您的回覆。 'integer expiresIn = response.getExpiresIn();'響應對象來自哪裏? –