Uso Retrofit per gestire la comunicazione con l'API del server, i token Web JSON dell'utente API per l'autenticazione. Il token scade di volta in volta, e sto cercando il modo migliore per implementare un Retrofit Client in grado di aggiornare automaticamente il token quando scade.Personalizzazione retrofit client per WebTokens
Questa è l'implementazione iniziale mi è venuta,:
/**
* 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");
}
}
Non conosco l'architettura di Retrofit/OkHttpClient profondamente, ma per quanto ho capito il metodo execute può essere chiamato più volte da multipla thread, lo OkClient
è la stessa condivisa tra Calls
ma viene eseguita solo una copia superficiale. Sto usando synchronized
nel metodo refreshToken()
per evitare l'inserimento di più thread in refreshToken()
ed effettuare più chiamate di accesso, un aggiornamento è necessario solo un thread dovrebbe rendere refreshCall e gli altri utilizzeranno il token rinnovato.
Non ho ancora provato sul serio, ma per quello che vedo è funzionante. Forse qualcuno ha già avuto questo problema e può condividere la sua soluzione, o può essere utile per qualcuno con lo stesso problema/simile.
Grazie.
E se si desidera utilizzare RX: http://stackoverflow.com/questions/25546934/retrofit-rxjava-and -sessione basata su servizi – Than
@ Sergio: Grazie per la meravigliosa risposta. Comunque il mio commento è in riferimento al codice che hai nella domanda. Solo curioso, se hai fatto un synchronous a 'refreshToken' chiamando di nuovo il login usando' Retrofit', non ha gettato 'NetworkOnMainThreadException', dal momento che la chiamata sincrona fatta da Retrofit è sul thread principale e Android non consente le chiamate di rete sul filo principale? Grazie in anticipo. –
@ShobhitPuri Ciao, il metodo 'refreshToken' è chiamato dentro 'execute' e questo metodo è chiamato in background dalla libreria Retrofit, non ricordo i dettagli ora. Ma è sicuro farlo. –