Integración con Android de Google+ – repetida UserRecoverableAuthException

Hemos contactado con Google sobre esto y estamos en el chat

El problema parece ser fijo para los dispositivos, excepto los teléfonos Samsung.

Estoy agregando una opción de inicio de sesión de Google+ a una aplicación según las instrucciones oficiales . Una vez que el usuario ha seleccionado su cuenta, quisiera que mi servidor recuperara su información de perfil de Google+ y actualizara su perfil en nuestro sitio para que coincida.

La primera parte – que el usuario seleccione una cuenta de Google localmente – parece funcionar bien. Cuando intento solicitar un token para la cuenta seleccionada, aparece el cuadro de diálogo de autenticación de Google con los parámetros apropiados; Sin embargo, cuando autorizo ​​la aplicación usando ese diálogo y GoogleAuthUtil.getToken(...) solicitar el token, GoogleAuthUtil.getToken(...) vuelve a lanzar una UserRecoverableAuthException ( NeedPermission , no GooglePlayServicesAvailabilityException ) y obtendré el mismo diálogo pidiéndome que apruebe!

Este comportamiento está presente en un Samsung S3 con Android 4.1.1 (con 3 cuentas de Google) y un Acer A100 con 4.0.3. No está presente en un glaciar HTC que ejecute 2.3.4. En su lugar, el Glaciar HTC me da un código de autorización válido. Todos los dispositivos cuentan con la última versión de Google Play Services instalada y utilizan diferentes cuentas de Google+.

¿Alguien ha visto esto antes? ¿Dónde puedo comenzar con la depuración?

Aquí está el código completo – es algo obviamente mal?

 public class MyGooglePlusClient { private static final String LOG_TAG = "GPlus"; private static final String SCOPES_LOGIN = Scopes.PLUS_LOGIN + " " + Scopes.PLUS_PROFILE; private static final String ACTIVITIES_LOGIN = "http://schemas.google.com/AddActivity"; private static MyGooglePlusClient myGPlus = null; private BaseActivity mRequestingActivity = null; private String mSelectedAccount = null; /** * Get the GPlus singleton * @return GPlus */ public synchronized static MyGooglePlusClient getInstance() { if (myGPlus == null) myGPlus = new MyGooglePlusClient(); return myGPlus; } public boolean login(BaseActivity requester) { Log.w(LOG_TAG, "Starting login..."); if (mRequestingActivity != null) { Log.w(LOG_TAG, "Login attempt already in progress."); return false; // Cannot launch a new request; already in progress } mRequestingActivity = requester; if (mSelectedAccount == null) { Intent intent = AccountPicker.newChooseAccountIntent(null, null, new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, false, null, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE, null, null); mRequestingActivity.startActivityForResult(intent, BaseActivity.REQUEST_GPLUS_SELECT); } return true; } public void loginCallback(String accountName) { mSelectedAccount = accountName; authorizeCallback(); } public void logout() { Log.w(LOG_TAG, "Logging out..."); mSelectedAccount = null; } public void authorizeCallback() { Log.w(LOG_TAG, "User authorized"); AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... params) { String token = null; try { Bundle b = new Bundle(); b.putString(GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES, ACTIVITIES_LOGIN); token = GoogleAuthUtil.getToken(mRequestingActivity, mSelectedAccount, "oauth2:server:client_id:"+Constants.GOOGLE_PLUS_SERVER_OAUTH_CLIENT +":api_scope:" + SCOPES_LOGIN, b); } catch (IOException transientEx) { // Network or server error, try later Log.w(LOG_TAG, transientEx.toString()); onCompletedLoginAttempt(false); } catch (GooglePlayServicesAvailabilityException e) { Log.w(LOG_TAG, "Google Play services not available."); Intent recover = e.getIntent(); mRequestingActivity.startActivityForResult(recover, BaseActivity.REQUEST_GPLUS_AUTHORIZE); } catch (UserRecoverableAuthException e) { // Recover (with e.getIntent()) Log.w(LOG_TAG, "User must approve "+e.toString()); Intent recover = e.getIntent(); mRequestingActivity.startActivityForResult(recover, BaseActivity.REQUEST_GPLUS_AUTHORIZE); } catch (GoogleAuthException authEx) { // The call is not ever expected to succeed Log.w(LOG_TAG, authEx.toString()); onCompletedLoginAttempt(false); } Log.w(LOG_TAG, "Finished with task; token is "+token); if (token != null) { authorizeCallback(token); } return token; } }; task.execute(); } public void authorizeCallback(String token) { Log.w(LOG_TAG, "Token obtained: "+token); // <snipped - do some more stuff involving connecting to the server and resetting the state locally> } public void onCompletedLoginAttempt(boolean success) { Log.w(LOG_TAG, "Login attempt "+(success ? "succeeded" : "failed")); mRequestingActivity.hideProgressDialog(); mRequestingActivity = null; } } 

He tenido este problema por un tiempo y se me ocurrió una solución adecuada.

 String token = GoogleAuthUtil.getToken(this, accountName, scopeString, appActivities); 

Esta línea devolverá el token de una vez o activará la excepción UserRecoverableAuthException. En la guía de inicio de sesión de Google Plus, se indica que se debe abrir la actividad de recuperación adecuada.

 startActivityForResult(e.getIntent(), RECOVERABLE_REQUEST_CODE); 

Cuando la actividad vuelve con el resultado, volverá con pocos extras en la intención y ahí es donde reside el nuevo token:

 @Override protected void onActivityResult(int requestCode, int responseCode, Intent intent) { if (requestCode == RECOVERABLE_REQUEST_CODE && responseCode == RESULT_OK) { Bundle extra = intent.getExtras(); String oneTimeToken = extra.getString("authtoken"); } } 

Con el nuevo oneTimeToken dado de la extra, puede enviar al servidor para conectarse correctamente.

¡Espero que esto ayude!

Es demasiado tarde para responder, pero puede ayudar a las personas que tienen la misma preocupación en el futuro.

Han mencionado en el tutorial que siempre lanzará UserRecoverableAuthException cuando invocas GoogleAuthUtil.getToken () por primera vez. La segunda vez tendrá éxito.

 catch (UserRecoverableAuthException e) { // Requesting an authorization code will always throw // UserRecoverableAuthException on the first call to GoogleAuthUtil.getToken // because the user must consent to offline access to their data. After // consent is granted control is returned to your activity in onActivityResult // and the second call to GoogleAuthUtil.getToken will succeed. startActivityForResult(e.getIntent(), AUTH_CODE_REQUEST_CODE); return; } 

Utilicé debajo del código para conseguir el código de acceso de google.

Ejecutar este new GetAuthTokenFromGoogle().execute(); Una vez desde public void onConnected(Bundle connectionHint) y una vez desde protected void onActivityResult(int requestCode, int responseCode, Intent intent)

 private class GetAuthTokenFromGoogle extends AsyncTask<Void, Integer, Void>{ @Override protected void onPreExecute() { } @Override protected Void doInBackground(Void... params) { // TODO Auto-generated method stub try { accessCode = GoogleAuthUtil.getToken(mContext, Plus.AccountApi.getAccountName(mGoogleApiClient), SCOPE); new ValidateTokenWithPhoneOmega().execute(); Log.d("Token -- ", accessCode); } catch (IOException transientEx) { // network or server error, the call is expected to succeed if you try again later. // Don't attempt to call again immediately - the request is likely to // fail, you'll hit quotas or back-off. return null; } catch (UserRecoverableAuthException e) { // Recover startActivityForResult(e.getIntent(), RC_ACCESS_CODE); e.printStackTrace(); } catch (GoogleAuthException authEx) { // Failure. The call is not expected to ever succeed so it should not be // retried. authEx.printStackTrace(); return null; } catch (Exception e) { throw new RuntimeException(e); } return null; } @Override protected void onPostExecute(Void result) { } } 

Tengo alrededor de este problema mediante un inicio de sesión basado en web. Abro una url como esta

 String url = "https://accounts.google.com/o/oauth2/auth?scope=" + Scopes.PLUS_LOGIN + "&client_id=" + webLoginClientId + "&response_type=code&access_type=offline&approval_prompt=force&redirect_uri=" + redirect; 

El url de redirección maneja la respuesta y vuelve a mi aplicación.

En cuanto a mis descubrimientos sobre el uso de los Servicios de Google Play, he encontrado:

HTC One es 3.1.59 (736673-30) – no funciona Galaxy Note es 3.1.59 (736673-36) – no funciona Nexus S es 3.1.59 (736673-34) – funciona

Y me gustaría estar involucrado en el chat que está ocurriendo, sin embargo no tengo una reputación suficientemente alta para hacerlo.

He experimentado el mismo problema recientemente – parece ser específico del dispositivo (que tenía que suceder cada vez que en un S3, pero en otro S3 que ejecuta el mismo sistema operativo que no sucedió, incluso con la misma cuenta). Mi intuición es que es un error en una aplicación cliente, ya sea la aplicación de G + o la aplicación de Google Play Services. Me las arreglé para solucionar el problema en uno de mis dispositivos mediante el restablecimiento de la fábrica (un Motorola Defy), luego reinstalar la aplicación de Servicios de Google Play, pero eso es una solución completamente inútil para decir a los usuarios.

Edit (6 de agosto de 2013): Esto parece haber sido arreglado para mí sin ningún cambio en mi código.

El primer problema potencial que puedo ver es que estás llamando a GoogleAuthUtil.getToken() después de recibir la devolución de llamada onConnected() . Esto es un problema porque solicitar un código de autorización para su servidor con GoogleAuthUtil.getToken() siempre mostrará una pantalla de consentimiento a sus usuarios. Por lo tanto, sólo debe obtener un código de autorización para los nuevos usuarios y, para evitar que aparezcan dos pantallas de consentimiento de los usuarios nuevos, debe obtener un código de autorización e intercambiarlo en su servidor antes de resolver cualquier PlusClient conexión de PlusClient .

En segundo lugar, asegúrese de que realmente necesita un PlusClient y un código de autorización para sus servidores. Sólo necesita obtener un PlusClient y un código de autorización si tiene la intención de realizar llamadas a las API de Google tanto desde el cliente de Android como desde su servidor. Como se explica en esta respuesta .

Estos problemas sólo darían lugar a que aparezcan dos diálogos de consentimiento (lo que claramente no es un bucle interminable) – ¿está viendo más de dos diálogos de consentimiento?

Tuve un problema similar en el que un bucle de autenticación aparente mantuvo la creación de {read: spamming} estos diálogos de solicitudes de " Firma en … " y de permiso al mismo tiempo que daba la excepción discutida repetidamente.

El problema aparece en algún código de ejemplo ligeramente modificado que yo (y otro como yo, sospecho) "cargo-culted" de AndroidHive . La solución que funcionó para mí fue garantizar que sólo una tarea de recuperación de token de fondo se ejecuta en el fondo en un momento dado.

Para hacer que mi código sea más fácil de seguir, aquí está el flujo de autorización en mi aplicación (que es casi idéntico al código de ejemplo de AndoidHive): Activity -> onConnected(...) -> getProfileInformation() -> getOneTimeToken() .

Aquí es donde getOneTimeToken() se llama:

 private void getProfileInformation() { try { if (Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null) { Person currentPerson = Plus.PeopleApi .getCurrentPerson(mGoogleApiClient); String personName = currentPerson.getDisplayName(); String personPhotoUrl = currentPerson.getImage().getUrl(); String personGooglePlusProfile = currentPerson.getUrl(); String email = Plus.AccountApi.getAccountName(mGoogleApiClient); getOneTimeToken(); // <------- ... 

Aquí está mi getOneTimeToken() :

 private void getOneTimeToken(){ if (task==null){ task = new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... params) { LogHelper.log('d',LOGTAG, "Executing background task...."); Bundle appActivities = new Bundle(); appActivities.putString( GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES, ACTIVITIES_LOGIN); String scopes = "oauth2:server" + ":client_id:" + SERVER_CLIENT_ID + ":api_scope:" + SCOPES_LOGIN; String token = null; try { token = GoogleAuthUtil.getToken( ActivityPlus.this, Plus.AccountApi.getAccountName(mGoogleApiClient), scopes, appActivities ); } catch (IOException transientEx) { /* Original comment removed*/ LogHelper.log('e',LOGTAG, transientEx.toString()); } catch (UserRecoverableAuthException e) { /* Original comment removed*/ LogHelper.log('e',LOGTAG, e.toString()); startActivityForResult(e.getIntent(), AUTH_CODE_REQUEST); } catch (GoogleAuthException authEx) { /* Original comment removed*/ LogHelper.log('e',LOGTAG, authEx.toString()); } catch (IllegalStateException stateEx){ LogHelper.log('e',LOGTAG, stateEx.toString()); } LogHelper.log('d',LOGTAG, "Background task finishing...."); return token; } @Override protected void onPostExecute(String token) { LogHelper.log('i',LOGTAG, "Access token retrieved: " + token); } }; } LogHelper.log('d',LOGTAG, "Task setup successful."); if(task.getStatus() != AsyncTask.Status.RUNNING){ task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); //double safety! } else LogHelper.log('d',LOGTAG, "Attempted to restart task while it is running!"); } 

Tenga en cuenta que tengo una doble seguridad (probablemente redundante) contra la tarea que se ejecuta varias veces:

  1. if(task .getStatus() != AsyncTask.Status.RUNNING){...} – asegura que la tarea no se está ejecutando antes de intentar ejecutarla.
  2. task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); – se asegura de que las copias de esta tarea estén "sincronizadas" (es decir, una cola está en su lugar tal que sólo una tarea de este tipo puede ejecutarse en un momento dado).

PS Menor aclaración: LogHelper.log('e',...) es equivalente a Log.e(...) etc.

Deberías startactiviy en el hilo de la interfaz de usuario

 try { .... } catch (IOException transientEx) { .... } catch (final UserRecoverableAuthException e) { .... runOnUiThread(new Runnable() { public void run() { startActivityForResult(e1.getIntent(), AUTH_CODE_REQUEST); } }); } 

Tenía el mismo error con bucle infinito de solicitud de permiso. Para mí fue porque el tiempo en mi teléfono estaba cambiado. Cuando compruebo el tiempo de detección automáticamente este error desapareció. ¡Espero que esto ayude!

  • 'Solicitud no registrada' mostrada en la pantalla de consentimiento de inicio de sesión de Google+
  • Cómo manejar el error redirect_uri_mismatch cuando la aplicación Android obtiene acceso sin conexión para el back-end de la Web?
  • ¿Twitter respalda OAuth 2.0?
  • Error "No se puede crear una conexión fiable con el servidor" al iniciar sesión con google plus
  • Oauth 2.0: identificación del cliente y secreto del cliente expuestos, ¿es un problema de seguridad?
  • Suscripción a Android y API de Google
  • Autenticar credenciales recibidas desde facebook en mi propia API REST
  • C2DM con PHP usando OAuth2.0 (ClientLogin está obsoleto!)
  • Javamail api en android usando XOauth
  • Autenticación de coordenadas de Google
  • Android: verifica el nombre de la cuenta google del dispositivo en el servidor
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.