web-dev-qa-db-fra.com

Intégration Android Google+ - UserRecoverableAuthException répétée

_ {Nous avons contacté Google à ce sujet et nous discutons

_ {Le problème semble être résolu pour les appareils sauf téléphones Samsung.}

J'ajoute une option de connexion Google+ à une application conformément aux instructions officielles . Une fois que l'utilisateur a sélectionné son compte, je souhaite que mon serveur récupère les informations de son profil Google+ et mette à jour son profil sur notre site afin qu'il corresponde.

La première partie - demander à l'utilisateur de sélectionner un compte Google localement - semble bien fonctionner. Lorsque j'essaie de demander un jeton pour le compte sélectionné, la boîte de dialogue Authentification Google s'affiche avec les paramètres appropriés. Cependant, lorsque j'autorise l'application à l'aide de cette boîte de dialogue et demande à nouveau le jeton, GoogleAuthUtil.getToken(...) jette à nouveau une UserRecoverableAuthException (NeedPermission, pas GooglePlayServicesAvailabilityException) et j'obtiens la même boîte de dialogue me demandant d'approuver!

Ce comportement est présent sur un Samsung S3 sous Android 4.1.1 (avec 3 comptes Google) et sur un Acer A100 sous 4.0.3. Il n'est PAS présent sur un HTC Glacier sous 2.3.4. Au lieu de cela, le HTC Glacier me donne un code d'authentification valide. Tous les appareils disposent de la dernière itération des services Google Play et utilisent différents comptes Google+.

Quelqu'un a vu ça avant? Où puis-je commencer avec le débogage?

Voici le code complet - est-ce que quelque chose ne va manifestement pas?

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;
}
}
28
Arkaaito

J'ai ce problème depuis un moment et j'ai trouvé une solution appropriée.

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

Cette ligne renverra le jeton ponctuel ou déclenchera l'exception UserRecoverableAuthException. Dans le guide de connexion de Google Plus, il est indiqué d'ouvrir l'activité de récupération appropriée.

startActivityForResult(e.getIntent(), RECOVERABLE_REQUEST_CODE);

Lorsque l'activité retourne avec le résultat, elle retourne avec peu d'extras dans l'intention et c'est là que réside le nouveau jeton:

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

Avec le nouveau oneTimeToken donné dans l'extra, vous pouvez soumettre au serveur pour vous connecter correctement.

J'espère que ça aide!

13
Calvin Park

Il est trop tard pour répondre, mais cela pourrait aider les personnes qui ont le même souci à l’avenir.

Ils ont mentionné dans le tutorial que le système générera toujours une exception UserRecoverableAuthException Lorsque vous appelez GoogleAuthUtil.getToken () pour la première fois. Deuxième fois ça va réussir.

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

j'ai utilisé le code ci-dessous pour obtenir le code d'accès de Google.

exécuter cette new GetAuthTokenFromGoogle().execute(); une fois à partir de public void onConnected(Bundle connectionHint) et une fois à partir de 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)  
        { 
        }
    }
5
appdroid

J'ai résolu ce problème en utilisant un identifiant basé sur le Web. J'ouvre une url comme ça

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;

L'URL de redirection gère ensuite la réponse et retourne à mon application.

En ce qui concerne mes conclusions sur l'utilisation des services Google Play, j'ai constaté:

HTC One est 3.1.59 (736673-30) - ne fonctionne pas Galaxy Note est 3.1.59 (736673-36) - ne fonctionne pas Nexus S est 3.1.59 (736673-34) - travaux 

Et j'aimerais participer au chat qui se déroule, mais je n'ai pas assez de réputation pour le faire.

2
user2608643

Edit (6 août 2013): Cela semble avoir été corrigé pour moi sans aucune modification de mon code.

Le premier problème potentiel que je peux voir est que vous appelez GoogleAuthUtil.getToken() après avoir reçu le rappel onConnected(). Ceci est un problème parce que demander un code d'autorisation pour votre serveur en utilisant GoogleAuthUtil.getToken() sera toujours affichera un écran de consentement à vos utilisateurs. Ainsi, vous ne devriez obtenir un code d'autorisation que pour les nouveaux utilisateurs. Pour éviter d'afficher deux écrans de consentement aux nouveaux utilisateurs, vous devez extraire un code d'autorisation et l'échanger sur votre serveur avant de résoudre tout problème de connexion à partir de PlusClient.

Deuxièmement, assurez-vous que vous avez réellement besoin d’un code PlusClient et d’un code d’autorisation pour vos serveurs. Vous devez uniquement obtenir un PlusClient et un code d'autorisation si vous avez l'intention d'appeler les API Google à partir du client Android et votre serveur. Comme expliqué dans cette réponse .

Ces problèmes ne feraient que faire apparaître deux boîtes de dialogue de consentement (ce qui n'est clairement pas une boucle sans fin) - voyez-vous plus de deux boîtes de dialogue de consentement? 

2
Lee

J'ai récemment rencontré le même problème - il semble être spécifique à un appareil (cela se produisait chaque fois sur un S3, mais sur un autre S3 exécutant le même système d'exploitation, cela ne s'est pas produit, même avec le même compte). Mon impression est qu'il s'agit d'un bogue dans une application client, l'application G + ou les services Google Play. J'ai réussi à résoudre le problème sur l'un de mes appareils en le réinitialisant en usine (un Motorola Defy), puis en réinstallant l'application Services Google Play, mais c'est une solution totalement inutile à informer des utilisateurs.

2
Adam

J'ai eu un problème similaire où une boucle d'authentification apparente continuait à créer {read: spamming} ces dialogues " Signing In ... " et Permission tout en donnant à plusieurs reprises l'exception évoquée.

Le problème apparaît dans un exemple de code légèrement modifié que je soupçonne (et d'autres comme moi) "cargo-culted" from AndroidHive . La solution qui a fonctionné pour moi était en veillant à ce qu'une seule tâche d'extraction de jetons en arrière-plan s'exécute en arrière-plan à un moment donné.

Pour rendre mon code plus facile à suivre, voici le flux d'authentification dans mon application (qui est presque identique à l'exemple de code sur AndoidHive): Activity -> onConnected(...) -> getProfileInformation() -> getOneTimeToken().

Voici où getOneTimeToken() est appelé:

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(); // <-------
            ...

Voici ma 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!");
}

Veuillez noter que je dispose d'une double sécurité {probablement redondante} par rapport à la tâche qui s'exécute plusieurs fois:

  1. if(task .getStatus() != AsyncTask.Status.RUNNING){...} - s'assure que la tâche n'est pas en cours d'exécution avant de tenter de l'exécuter.
  2. task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);- s'assure que les copies de cette tâche sont "synchronisées" (c'est-à-dire qu'une file d'attente est en place de sorte qu'une seule tâche de ce type puisse être exécutée à un moment donné).

P.S. Précision mineure: LogHelper.log('e',...) est équivalent à Log.e(...) etc.

1
Dev-iL

vous devriez commencer à activer dans le fil de l'interface utilisateur

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

Avait le même bug avec la boucle infinie de demande d'autorisation. Pour moi, c'était parce que l'heure sur mon téléphone était décalée. Lorsque je vérifie le temps de détection automatiquement, ce bogue a disparu. J'espère que cela t'aides!

0
user2410066