Używam funkcji Retrofit do obsługi komunikacji z interfejsem API serwera, JSON Web Token użytkownika interfejsu API do uwierzytelniania. Token wygasa od czasu do czasu i szukam najlepszego sposobu wdrożenia klienta aktualizacji, który może odświeżać token automatycznie po jego wygaśnięciu.Retrofit klienta niestandardowego do uwierzytelniania WebTokens
Jest to wstępna implementacja wymyśliłem,:
/**
* 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");
}
}
nie wiem architekturę modernizacyjny/OkHttpClient głęboko, ale o ile mogę zrozumieć sposób wykonać można nazwać wiele razy z wielu wątków,jest taki sam udostępniony między Calls
tylko płytka kopia jest wykonywana. Używam metody synchronized
w refreshToken()
, aby uniknąć wielu wątków, aby wprowadzić w refreshToken()
i wykonać wiele połączeń logowania, i odświeżenie jest potrzebne tylko jeden wątek powinien uczynić refreshCall, a inni będą używać odnowionego tokena.
Nie testowałem tego jeszcze poważnie, ale to, co widzę, działa dobrze. Może ktoś już miał ten problem i może dzielić się swoim rozwiązaniem lub może być pomocny dla kogoś z tym samym/podobnym problemem.
Dzięki.
A jeśli chcesz używać RX: http://stackoverflow.com/questions/25546934/retrofit-rxjava-and -sesja-usługi-usługi – Than
@Sergio: Dzięki za cudowną odpowiedź. Jednak mój komentarz odnosi się do kodu, który masz w pytaniu. Po prostu ciekawy, czy dokonałeś synchronizacji z 'refreshToken' przez ponowne wywołanie loginu za pomocą' Retrofit', czy nie wyrzucił 'NetworkOnMainThreadException', ponieważ wywołanie synchroniczne wykonane przez Retrofit znajduje się w głównym wątku, a Android nie zezwala na połączenia sieciowe w głównym wątku? Z góry dziękuję. –
@ShobhitPuri Cześć, metoda 'refreshToken' jest wywoływana wewnątrz' execute' i ta metoda jest wywoływana w tle przez bibliotekę Retrofit, teraz nie pamiętam szczegółów. Ale można to bezpiecznie zrobić. –