7
我正在使用Retrofit處理與服務器API,API用戶JSON Web令牌進行身份驗證的通信。令牌會不時失效,我正在尋找實現改進客戶端的最佳方法,當客戶端到期時可以自動刷新令牌。爲WebToken驗證改造自定義客戶端
這是首次執行,我想出了,:
/**
* Client implementation that refreshes JSON WebToken automatically if
* the response contains a 401 header, has there may be simultaneous calls to execute method
* the refreshToken is synchronized to avoid multiple login calls.
*/
public class RefreshTokenClient extends OkClient {
private static final int UNAUTHENTICATED = 401;
/**
* Application context
*/
private Application mContext;
public RefreshTokenClient(OkHttpClient client, Application application) {
super(client);
mContext = application;
}
@Override
public Response execute(Request request) throws IOException {
Timber.d("Execute request: " + request.getMethod() + " - " + request.getUrl());
//Make the request and check for 401 header
Response response = super.execute(request);
Timber.d("Headers: "+ request.getHeaders());
//If we received a 401 header, and we have a token, it's most likely that
//the token we have has expired
if(response.getStatus() == UNAUTHENTICATED && hasToken()) {
Timber.d("Received 401 from server awaiting");
//Clear the token
clearToken();
//Gets a new token
refreshToken(request);
//Update token in the request
Timber.d("Make the call again with the new token");
//Makes the call again
return super.execute(rebuildRequest(request));
}
return response;
}
/**
* Rebuilds the request to be executed, overrides the headers with the new token
* @param request
* @return new request to be made
*/
private Request rebuildRequest(Request request){
List<Header> newHeaders = new ArrayList<>();
for(Header h : request.getHeaders()){
if(!h.getName().equals(Constants.Headers.USER_TOKEN)){
newHeaders.add(h);
}
}
newHeaders.add(new Header(Constants.Headers.USER_TOKEN,getToken()));
newHeaders = Collections.unmodifiableList(newHeaders);
Request r = new Request(
request.getMethod(),
request.getUrl(),
newHeaders,
request.getBody()
);
Timber.d("Request url: "+r.getUrl());
Timber.d("Request new headers: "+r.getHeaders());
return r;
}
/**
* Do we have a token
*/
private boolean hasToken(){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
return prefs.contains(Constants.TOKEN);
}
/**
* Clear token
*/
private void clearToken(){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
prefs.edit().remove(Constants.TOKEN).commit();
}
/**
* Saves token is prefs
*/
private void saveToken(String token){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
prefs.edit().putString(Constants.TOKEN, token).commit();
Timber.d("Saved new token: " + token);
}
/**
* Gets token
*/
private String getToken(){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
return prefs.getString(Constants.TOKEN,"");
}
/**
* Refreshes the token by making login again,
* //TODO implement refresh token endpoint, instead of making another login call
*/
private synchronized void refreshToken(Request oldRequest) throws IOException{
//We already have a token, it means a refresh call has already been made, get out
if(hasToken()) return;
Timber.d("We are going to refresh token");
//Get credentials
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
String email = prefs.getString(Constants.EMAIL, "");
String password = prefs.getString(Constants.PASSWORD, "");
//Login again
com.app.bubbles.model.pojos.Response<Login> res = ((App) mContext).getApi().login(
new com.app.bubbles.model.pojos.Request<>(credentials)
);
//Save token in prefs
saveToken(res.data.getTokenContainer().getToken());
Timber.d("Token refreshed");
}
}
我不知道改造/ OkHttpClient的體系結構深刻,但據我瞭解execute方法可以調用來自多個多次線程,OkClient
與Calls
之間共享的只是淺拷貝完成。 我在refreshToken()
方法中使用方法來避免多個線程進入refreshToken()
並進行多次登錄調用,我需要一次刷新,只有一個線程應該使refreshCall和其他人將使用更新的令牌。
我還沒有認真測試過它,但是對於我能看到它工作正常。也許有人已經遇到過這個問題,可以分享他的解決方案,或者對於有相同/相似問題的人有幫助。
謝謝。
並且如果要使用RX:http://stackoverflow.com/questions/25546934/retrofit-rxjava-and基於激情的服務 – Than
@Sergio:謝謝你的精彩回答。不過,我的評論是針對您在問題中提供的代碼。只是好奇,如果通過使用Retrofit再次調用登錄來同步refreshToken,那麼它不會拋出NetworkOnMainThreadException,因爲Retrofit的同步調用位於主線程上,Android不允許網絡調用在主線上?提前致謝。 –
@ShobhitPuri嗨,'refreshToken'方法在'execute'裏面調用,並且這個方法在後臺被Retrofit庫調用,現在不記得細節。但是做到這一點是安全的。 –