implement how funciona example como android oauth-2.0 retrofit

android - how - oauth2 como funciona



Actualizar el token de OAuth con Retrofit sin modificar todas las llamadas (6)

TokenAuthenticator depende de una clase de servicio. La clase de servicio depende de una instancia OkHttpClient. Para crear un OkHttpClient necesito el TokenAuthenticator. ¿Cómo puedo romper este ciclo? ¿Dos OkHttpClients diferentes? Ellos van a tener diferentes grupos de conexiones ..

Si tiene, por ejemplo, un Retrofit TokenService que necesita dentro de su Authenticator pero solo desea configurar un OkHttpClient , puede usar un TokenServiceHolder como una dependencia para TokenAuthenticator . Debería mantener una referencia en el nivel de aplicación (singleton). Esto es fácil si está usando Dagger 2; de lo contrario, simplemente cree un campo de clase dentro de su Aplicación.

En TokenAuthenticator.java

public class TokenAuthenticator implements Authenticator { private final TokenServiceHolder tokenServiceHolder; public TokenAuthenticator(TokenServiceHolder tokenServiceHolder) { this.tokenServiceHolder = tokenServiceHolder; } @Override public Request authenticate(Proxy proxy, Response response) throws IOException { //is there a TokenService? TokenService service = tokenServiceHolder.get(); if (service == null) { //there is no way to answer the challenge //so return null according to Retrofit''s convention return null; } // Refresh your access_token using a synchronous api request newAccessToken = service.refreshToken().execute(); // Add new header to rejected request and retry it return response.request().newBuilder() .header(AUTHORIZATION, newAccessToken) .build(); } @Override public Request authenticateProxy(Proxy proxy, Response response) throws IOException { // Null indicates no attempt to authenticate. return null; }

En TokenServiceHolder.java :

public class TokenServiceHolder { TokenService tokenService = null; @Nullable public TokenService get() { return tokenService; } public void set(TokenService tokenService) { this.tokenService = tokenService; } }

Configuración del cliente:

//obtain instance of TokenServiceHolder from application or singleton-scoped component, then TokenAuthenticator authenticator = new TokenAuthenticator(tokenServiceHolder); OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.setAuthenticator(tokenAuthenticator); Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .client(okHttpClient) .build(); TokenService tokenService = retrofit.create(TokenService.class); tokenServiceHolder.set(tokenService);

Si está utilizando Dagger 2 o un marco de inyección de dependencia similar, hay algunos ejemplos en las respuestas a esta pregunta

Estamos utilizando Retrofit en nuestra aplicación de Android para comunicarnos con un servidor seguro de OAuth2. Todo funciona muy bien, usamos RequestInterceptor para incluir el token de acceso con cada llamada. Sin embargo, habrá momentos en los que el token de acceso caducará y el token deberá actualizarse. Cuando el token caduque, la próxima llamada volverá con un código HTTP no autorizado, por lo que es fácil de controlar. Podríamos modificar cada llamada de Retrofit de la siguiente manera: En la devolución de llamada fallida, verifique el código de error, si es No autorizado, actualice el token de OAuth y luego repita la llamada de Retrofit. Sin embargo, para esto, todas las llamadas deberían modificarse, lo cual no es una solución fácil de mantener y buena. ¿Hay alguna forma de hacerlo sin modificar todas las llamadas de Retrofit?


Después de una larga investigación, personalicé el cliente Apache para manejar Refreshing AccessToken For Retrofit En el que envía token de acceso como parámetro.

Inicie su adaptador con Cookie Persistent Client

restAdapter = new RestAdapter.Builder() .setEndpoint(SERVER_END_POINT) .setClient(new CookiePersistingClient()) .setLogLevel(RestAdapter.LogLevel.FULL).build();

Cookie Persistent Client que mantiene cookies para todas las solicitudes y verificaciones con cada respuesta de solicitud, si es un acceso no autorizado ERROR_CODE = 401, actualiza el token de acceso y recupera la solicitud, de lo contrario solo procesa la solicitud.

private static class CookiePersistingClient extends ApacheClient { private static final int HTTPS_PORT = 443; private static final int SOCKET_TIMEOUT = 300000; private static final int CONNECTION_TIMEOUT = 300000; public CookiePersistingClient() { super(createDefaultClient()); } private static HttpClient createDefaultClient() { // Registering https clients. SSLSocketFactory sf = null; try { KeyStore trustStore = KeyStore.getInstance(KeyStore .getDefaultType()); trustStore.load(null, null); sf = new MySSLSocketFactory(trustStore); sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); } catch (KeyManagementException e) { e.printStackTrace(); } catch (UnrecoverableKeyException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } HttpParams params = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT); SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("https", sf, HTTPS_PORT)); // More customization (https / timeouts etc) can go here... ClientConnectionManager cm = new ThreadSafeClientConnManager( params, registry); DefaultHttpClient client = new DefaultHttpClient(cm, params); // Set the default cookie store client.setCookieStore(COOKIE_STORE); return client; } @Override protected HttpResponse execute(final HttpClient client, final HttpUriRequest request) throws IOException { // Set the http context''s cookie storage BasicHttpContext mHttpContext = new BasicHttpContext(); mHttpContext.setAttribute(ClientContext.COOKIE_STORE, COOKIE_STORE); return client.execute(request, mHttpContext); } @Override public Response execute(final Request request) throws IOException { Response response = super.execute(request); if (response.getStatus() == 401) { // Retrofit Callback to handle AccessToken Callback<AccessTockenResponse> accessTokenCallback = new Callback<AccessTockenResponse>() { @SuppressWarnings("deprecation") @Override public void success( AccessTockenResponse loginEntityResponse, Response response) { try { String accessToken = loginEntityResponse .getAccessToken(); TypedOutput body = request.getBody(); ByteArrayOutputStream byte1 = new ByteArrayOutputStream(); body.writeTo(byte1); String s = byte1.toString(); FormUrlEncodedTypedOutput output = new FormUrlEncodedTypedOutput(); String[] pairs = s.split("&"); for (String pair : pairs) { int idx = pair.indexOf("="); if (URLDecoder.decode(pair.substring(0, idx)) .equals("access_token")) { output.addField("access_token", accessToken); } else { output.addField(URLDecoder.decode( pair.substring(0, idx), "UTF-8"), URLDecoder.decode( pair.substring(idx + 1), "UTF-8")); } } execute(new Request(request.getMethod(), request.getUrl(), request.getHeaders(), output)); } catch (IOException e) { e.printStackTrace(); } } @Override public void failure(RetrofitError error) { // Handle Error while refreshing access_token } }; // Call Your retrofit method to refresh ACCESS_TOKEN refreshAccessToken(GRANT_REFRESH,CLIENT_ID, CLIENT_SECRET_KEY,accessToken, accessTokenCallback); } return response; } }


Para cualquier persona que desee resolver llamadas concurrentes / paralelas al actualizar el token. Aquí hay una solución

class TokenAuthenticator: Authenticator { override fun authenticate(route: Route?, response: Response?): Request? { response?.let { if (response.code() == 401) { while (true) { if (!isRefreshing) { val requestToken = response.request().header(AuthorisationInterceptor.AUTHORISATION) val currentToken = OkHttpUtil.headerBuilder(UserService.instance.token) currentToken?.let { if (requestToken != currentToken) { return generateRequest(response, currentToken) } } val token = refreshToken() token?.let { return generateRequest(response, token) } } } } } return null } private fun generateRequest(response: Response, token: String): Request? { return response.request().newBuilder() .header(AuthorisationInterceptor.USER_AGENT, OkHttpUtil.UA) .header(AuthorisationInterceptor.AUTHORISATION, token) .build() } private fun refreshToken(): String? { synchronized(TokenAuthenticator::class.java) { UserService.instance.token?.let { isRefreshing = true val call = ApiHelper.refreshToken() val token = call.execute().body() UserService.instance.setToken(token, false) isRefreshing = false return OkHttpUtil.headerBuilder(token) } } return null } companion object { var isRefreshing = false } }


Puede intentar crear una clase base para todos sus cargadores en la que pueda detectar una excepción en particular y actuar según lo necesite. Haga que todos los cargadores diferentes se extiendan desde la clase base para distribuir el comportamiento.


Si está utilizando Retrofit > = 1.9.0 , puede utilizar OkHttp''s nuevo github.com/square/okhttp/wiki/Interceptors de OkHttp''s , que se introdujo en OkHttp 2.2.0 . Desearía utilizar un Interceptor de aplicación , que le permite retry and make multiple calls a retry and make multiple calls .

Su interceptor podría parecerse a este pseudocódigo:

public class CustomInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); // try the request Response response = chain.proceed(request); if (response shows expired token) { // get a new token (I use a synchronous Retrofit call) // create a new request and modify it accordingly using the new token Request newRequest = request.newBuilder()...build(); // retry the request return chain.proceed(newRequest); } // otherwise just pass the original response on return response; } }

Después de definir su Interceptor , cree un OkHttpClient y agregue el interceptor como Interceptor de Aplicación .

OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.interceptors().add(new CustomInterceptor());

Y finalmente, use este OkHttpClient al crear su RestAdapter .

RestService restService = new RestAdapter().Builder ... .setClient(new OkClient(okHttpClient)) .create(RestService.class);

Advertencia: como menciona Jesse Wilson (de Square) here , esta es una cantidad de poder peligrosa.

Habiendo dicho eso, definitivamente creo que esta es la mejor manera de manejar algo como esto ahora. Si tiene alguna pregunta, no dude en preguntar en un comentario.


Por favor, no use Interceptors para tratar con la autenticación.

Actualmente, el mejor enfoque para manejar la autenticación es usar la nueva API Authenticator , diseñada específicamente para este propósito .

OkHttp le pedirá automáticamente al Authenticator credenciales cuando la respuesta sea 401 Not Authorised reintentando la última solicitud fallida con ellas.

public class TokenAuthenticator implements Authenticator { @Override public Request authenticate(Proxy proxy, Response response) throws IOException { // Refresh your access_token using a synchronous api request newAccessToken = service.refreshToken(); // Add new header to rejected request and retry it return response.request().newBuilder() .header(AUTHORIZATION, newAccessToken) .build(); } @Override public Request authenticateProxy(Proxy proxy, Response response) throws IOException { // Null indicates no attempt to authenticate. return null; }

Adjunte un Authenticator a un OkHttpClient la misma manera que lo hace con los Interceptors

OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.setAuthenticator(authAuthenticator);

Utilice este cliente al crear su RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(ENDPOINT) .setClient(new OkClient(okHttpClient)) .build(); return restAdapter.create(API.class);