2015-10-03 18 views
7

Kimlik doğrulama için API kullanıcısı JSON Web Jetonları ile iletişim kurmak için Retrofit kullanıyorum. Simge zaman zaman sona eriyor ve zaman aşımına uğradığında jetonu otomatik olarak yenileyen bir Retrofit Client uygulamasının en iyi yolunu arıyorum.Retrofit özel WebTokens kimlik doğrulaması için istemci

Bu şimdiye ile geldi ilk uygulama, geçerli:

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


} 

derinden Retrofitler/OkHttpClient mimarisini bilmiyorum ama bildiğim kadarıyla ı yürütmek yöntem birden gelen birden çok kez çağrılabilir anlıyorum Konu, OkClientCalls arasında paylaşılan aynı sadece sığ bir kopya yapılır. refreshToken() birden çok iş parçacığı girip birden çok iş parçacığının girmesini önlemek için yöntemrefreshToken()10 yöntemini kullanıyorum ve yenileme işlemi yalnızca bir iş parçacığı refreshCall yapmalı ve diğerleri yenilenmiş belirtecini kullanmalıdır.

Henüz ciddiye almadım, ama görebildiğim için iyi çalışıyor. Belki birileri zaten bu sorunu vardı ve onun çözümünü paylaşabilir, ya da aynı/benzer bir sorunu olan biri için yararlı olabilir.

Teşekkürler.

bu bulmak herkes için

cevap

7

, sen OkHttp Interceptor ile gitmek veya bu kimlik doğrulama kadar isteklerini engellemek istiyorsanız Retrofitler GitHub sayfa

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(); 
} 

bir örnektir Şifrematik API

kullanmalıdır bitti, benim cevapta yaptım aynı senkronizasyon mekanizması kullanabilirsiniz çünkü interceptors aynı anda birden çok iş parçacığı üzerinde çalışabilir

+1

Ve RX kullanmak istiyorsanız: http://stackoverflow.com/questions/25546934/retrofit-rxjava-and -servis tabanlı hizmetler – Than

+0

@Sergio: Harika cevap için teşekkürler. Ancak yorumum, sorgunuzdaki kodla ilgili. Sadece 'Retrofit' kullanarak tekrar giriş yapıp 'refreshToken' ile senkronize ederseniz, 'SimpleTicen tarafından yapılan eşzamanlı çağrı' ana iş parçasında ve Android'in ağ aramalarına izin vermemesinden dolayı 'NetworkOnMainThreadException' komutunu atmadı. ana iş parçacığı üzerinde? Şimdiden teşekkürler. –

+0

@ShobhitPuri Merhaba, 'refreshToken' yöntemi' execute' içinde çağrılır ve bu metot Retrofit kütüphanesi tarafından arka planda çağrılır, şimdi detayları hatırlamayın. Ama bunu yapmak güvenli. –

İlgili konular