Override WebTokens User Authentication Client

I use Retrofit to handle communication with the server API, a user of the JSON APIs for authentication. The token expires from time to time, and I'm looking for the best way to implement a Retrofit Client, which can update the token automatically when it expires.

This is the initial implementation I came with:

/** * 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 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"); } } 

I do not know the architecture of Retrofit / OkHttpClient very well, but as far as I understand, the execute method can be called several times from several threads, OkClient is the same common for Calls that only a small copy is made. I use the synchronized method in refreshToken() to avoid multiple threads to enter refreshToken() and make several calls to enter the system, only the update is required, only one thread needs to do refreshCall, and the rest will use the updated token.

I have not experienced this seriously yet, but for what I see, it works fine. Maybe someone had this problem and can share their solution, or it might be useful for someone with the same / similar problem.

Thanks.

+7
android multithreading retrofit
source share
1 answer

For those who find this, you should go with OkHttp Interceptors or use the authentication API

This is an example from the Retrofit GitHub page

 public void setup() { OkHttpClient client = new OkHttpClient(); client.interceptors().add(new TokenInterceptor(tokenManager)); Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .client(client) .baseUrl("http://localhost") .build(); } private static class TokenInterceptor implements Interceptor { private final TokenManager mTokenManager; private TokenInterceptor(TokenManager tokenManager) { mTokenManager = tokenManager; } @Override public Response intercept(Chain chain) throws IOException { Request initialRequest = chain.request(); Request modifiedRequest = request; if (mTokenManager.hasToken()) { modifiedRequest = request.newBuilder() .addHeader("USER_TOKEN", mTokenManager.getToken()) .build(); } Response response = chain.proceed(modifiedRequest); boolean unauthorized = response.code() == 401; if (unauthorized) { mTokenManager.clearToken(); String newToken = mTokenManager.refreshToken(); modifiedRequest = request.newBuilder() .addHeader("USER_TOKEN", mTokenManager.getToken()) .build(); return chain.proceed(modifiedRequest); } return response; } } interface TokenManager { String getToken(); boolean hasToken(); void clearToken(); String refreshToken(); } 

If you want to block requests until authentication is done, you can use the same synchronization mechanism that I did in my answer, because interceptors can be launched simultaneously on several threads.

+7
source share

All Articles