Android Google+集成 - 重复UserRecoverableAuthException

时间:2013-07-18 02:18:20

标签: android oauth-2.0 google-plus

我们已就此与we are on chat

联系Google

设备除了三星手机之外,似乎已解决此问题。

我正按official instructions为应用添加Google+登录选项。一旦用户选择了他们的帐户,我希望我的服务器检索他们的Google+个人资料信息,并在我们的网站上更新他们的个人资料以匹配。

第一部分 - 让用户在本地选择Google帐户 - 似乎工作得很好。当我尝试为所选帐户请求令牌时,Google身份验证对话框会显示相应的参数;但是,当我使用该对话框授权应用并重新请求令牌时,GoogleAuthUtil.getToken(...)再次抛出UserRecoverableAuthExceptionNeedPermission,而不是GooglePlayServicesAvailabilityException),我得到相同的对话框请我批准!

此行为出现在运行Android 4.1.1(包含3个Google帐户)的Samsung S3和运行4.0.3的Acer A100上。它不存在于运行2.3.4的HTC Glacier上。相反,HTC Glacier为我提供了有效的身份验证码。所有设备都安装了最新版本的Google Play服务,并使用不同的Google+帐户。

之前有人见过吗?我在哪里可以开始调试?

这是完整的代码 - 显然是什么错误?

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

8 个答案:

答案 0 :(得分:13)

我已经有一段时间没有这个问题了,并提出了一个合适的解决方案。

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

此行将返回一次性令牌或将触发UserRecoverableAuthException。 在Google Plus登录指南中,它表示要打开正确的恢复活动。

startActivityForResult(e.getIntent(), RECOVERABLE_REQUEST_CODE);

当活动返回结果时,它将返回意图中的少量额外内容,这是新令牌所在的位置:

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

使用额外提供的新oneTimeToken,您可以提交给服务器以正确连接。

我希望这有帮助!

答案 1 :(得分:5)

回复为时已晚,但对未来有同样担忧的人可能会有所帮助。

他们在tutorial中提到它总会抛出UserRecoverableAuthException 当你第一次调用GoogleAuthUtil.getToken()时。第二次它会成功。

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

我使用下面的代码从谷歌获取访问代码。

new GetAuthTokenFromGoogle().execute();执行此public void onConnected(Bundle connectionHint)一次,从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)  
        { 
        }
    }

答案 2 :(得分:2)

编辑(2013年8月6日):这似乎已经为我修复,我的代码没有任何更改。

我可以看到的第一个潜在问题是,在您获得GoogleAuthUtil.getToken()回调后,您正在调用onConnected()。这是一个问题,因为使用GoogleAuthUtil.getToken()为您的服务器请求授权代码始终会向您的用户显示同意屏幕。因此,您只应获取新用户的授权代码,并且为了避免向新用户显示两个同意屏幕,您必须获取授权代码并在解决PlusClient的任何连接失败之前在服务器上进行交换。

其次,确保您确实需要服务器的PlusClient和授权代码。如果您打算从Android客户端服务器调用Google API,则只需获取PlusClient和授权码即可。如this answer中所述。

这些问题只会导致显示两个同意对话框(显然不是无限循环) - 您是否看到两个以上的同意对话框?

答案 3 :(得分:2)

我通过使用基于Web的登录解决了这个问题。我打开这样的网址

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;

重定向网址然后处理响应并返回到我的应用程序。

就我使用Google Play服务的调查结果而言,我发现:

HTC One是3.1.59(736673-30) - 无效 Galaxy Note是3.1.59(736673-36) - 不工作 Nexus S是3.1.59(736673-34) - 作品

我想参与正在进行的聊天,但是我没有足够的声誉这样做。

答案 4 :(得分:2)

我最近遇到了同样的问题 - 它似乎是特定于设备的(我每次都在一个S3上发生这种情况,但是在运行相同操作系统的另一个S3上它没有发生,即使使用相同的帐户) 。我的预感是它是客户端应用程序中的一个错误,无论是G +应用程序还是Google Play服务应用程序。我设法通过工厂重置它(摩托罗拉Defy)解决了我的一个设备上的问题,然后重新安装了Google Play服务应用程序,但这是一个完全没用的解决方案告诉用户。

答案 5 :(得分:1)

我遇到了类似的问题,其中明显的auth循环不断创建{read:spamming}这些“登录... ”和权限请求对话框,同时还重复发出讨论的异常。

问题出现在一些稍微修改过的示例代码中,我{和其他像我一样,我怀疑} "cargo-culted"来自AndroidHive。对我有用的解决方案是确保在任何给定时间只有一个后台令牌检索任务在后台运行。

为了让我的代码更容易理解,这里是我的应用程序中的auth流程(几乎与AndoidHive上的示例代码相同):Activity - &gt; onConnected(...) - &gt; getProfileInformation() - &gt; getOneTimeToken()

以下是调用getOneTimeToken()的地方:

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

这是我的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!");
}

请注意,我对多次执行的任务有{可能冗余}的双重安全性:

  1. if(task .getStatus() != AsyncTask.Status.RUNNING){...} - 确保在尝试执行任务之前未运行该任务。
  2. task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); - 确保此任务的副本是“同步的”(即队列已到位,以便在给定时间只能执行此类任务)。
  3. P.S。 轻微澄清:LogHelper.log('e',...)相当于Log.e(...)等。

答案 6 :(得分:0)

你应该在UI线程中启动

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

答案 7 :(得分:0)

与无限循环的权限请求有相同的错误。对我来说,这是因为手机上的时间被转移了。当我自动检查检测时间时,这个bug就消失了。希望这有帮助!