android - Google+登录跨客户端(android/web)身份验证

标签 android google-api-php-client google-api-client

我正在尝试将“使用 Google 登录”集成到具有 android 和 Web 组件的应用程序中。通过以下步骤,Web 组件中的所有内容都可以正常工作: 1. 使用防伪 token 、客户端 ID 和应用程序名称渲染 View 。

$state = md5(rand());
Session::set('state', $state);
$this->view->render('login', array(
    'CLIENT_ID' => 'my_web_client_id',
    'STATE' => $state,
    'APPLICATION_NAME' => 'my_app_name'));

2.当用户点击Google的登录按钮时,从Google的服务器获取一次性代码并将其发送到我的服务器。 3. 当我的服务器使用 https://github.com/google/google-api-php-client 收到一次性代码后使用该代码对用户进行身份验证。

if ($_SESSION['state'] != $_POST['state']) { // Where state is the anti-forgery token
  return 'some error';
}

$code = $_POST['code'];
$client = new Google_Client();
$client->setApplicationName("my_app_name");
$client->setClientId('my_web_client_id');
$client->setClientSecret('client_secret');
$client->setRedirectUri('postmessage');
$client->addScope("https://www.googleapis.com/auth/urlshortener");
$client->authenticate($code);

$token = json_decode($client->getAccessToken());
// Verify the token
$reqUrl = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' . $token->access_token;
$req = new Google_Http_Request($reqUrl);          
$tokenInfo = json_decode($client->getAuth()->authenticatedRequest($req)->getResponseBody());

// If there was an error in the token info, abort.
if ($tokenInfo->error) {
  return 'some error';
}

// Make sure the token we got is for our app.
if ($tokenInfo->audience != "my_web_client_id") {
  return 'some error';
}

// Saving user in db
...
// Load the app view

现在,Android 客户端应该类似吧?遵循这些教程:https://developers.google.com/+/mobile/android/sign-inhttp://www.androidhive.info/2014/02/android-login-with-google-plus-account-1/

onConnected方法中执行异步任务

class CreateToken extends AsyncTask<Void, Void, String> {

    @Override
    protected String doInBackground(Void... voids) {
        oneTimeCode = getOneTimeCode();
        String email = getUserGPlusEmail();
        try {
            // Opens connection and sends the one-time code and email to the server with 'POST' request
            googleLogin(oneTimeCode, email);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return oneTimeCode;
    }
}

private String getOneTimeCode() {

    String scopes = "oauth2:server:client_id:" + SERVER_CLIENT_ID + ":api_scope:" + SCOPE_EMAIL;
    String code = null;
    try {
        code = GoogleAuthUtil.getToken(
                LoginActivity.this,                                // Context context
                Plus.AccountApi.getAccountName(mGoogleApiClient),  // String accountName
                scopes                                             // String scope
        );

    } catch (IOException transientEx) {
        Log.e(Constants.TAG, "IOException");
        transientEx.printStackTrace();
        // 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.
    } catch (UserRecoverableAuthException e) {
        Log.e(Constants.TAG, "UserRecoverableAuthException");
        e.printStackTrace();
        // 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);
    } catch (GoogleAuthException authEx) {
        // Failure. The call is not expected to ever succeed so it should not be
        // retried.
        Log.e(Constants.TAG, "GoogleAuthException");
        authEx.printStackTrace();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

    Log.e(Constants.TAG, "ONE TIME CODE: " + code);
    return code;
}

成功获取code后,发送到我的服务器进行认证。 这是服务器上的代码:

$code = $_POST['code'];
$client = new Google_Client();
$client->setApplicationName("my_app_name");
$client->setClientId('my_web_client_id');  // Web component's client id
$client->setClientSecret('client_secret'); // Web component's secret
$client->addScope("email");
$client->setAccessType("offline");
$client->authenticate($code);
...

问题是身份验证每 10-15 分钟只能进行一次。当尝试在 10-15 分钟内多次获取一次性代码时,我得到与上一次相同的代码(显然有问题。这种情况仅发生在 Android 客户端上,我收到此错误:获取 OAuth2 访问 token 时出错,消息:'invalid_grant: i')。在 SO 中找不到有同样问题的人。可能我做错了什么,但不知道它是什么......任何帮助将不胜感激。

最佳答案

您不应该每次都发送代码。在网络上,这没什么问题,因为当您第一次同意时,您将获得一个可以让您离线访问的代码(当您交换它时,您将在响应中看到刷新 token ),但在将来的情况下您不会。在 Android 上,您会得到一个代码,每次都会给您一个刷新 token ,这意味着您每次都需要显示同意,并且您可能会遇到每个用户的限制或缓存问题(正如您所看到的那样) )。

您需要的神奇额外组件是一个称为 ID token 的东西。您可以在两个平台上轻松获取此信息,并告诉您此人是谁。查看此博文了解更多信息:http://www.riskcompletefailure.com/2013/11/client-server-authentication-with-id.html

ID token 的限制是您无法使用它来调用 Google API。它所做的只是为您提供 Google 用户 ID、正在使用的应用程序的客户端 ID 以及(如果使用电子邮件范围)电子邮件地址。好处是,您可以在所有平台上轻松获得一个,只需较少的用户交互,并且它们是经过加密签名的,因此大多数时候您可以使用它们,而无需在服务器上进行任何进一步的网络调用。如果您不需要进行 Google API 调用(因为您只是将其用于身份验证),那么这是迄今为止最好的选择 - 考虑到您刚刚收到电子邮件,我倾向于在此停止。

如果您需要从服务器进行 Google API 调用,那么您应该使用该代码 - 但仅一次。当您交换它时,您将刷新 token 存储在根据用户 ID 键入的数据库中。然后,当用户回来时,您会查找刷新 token 并使用它来生成新的访问 token 。所以流程是:

第一次:

  1. Android -> 服务器:id token
  2. 服务器 -> 我没有刷新 token !
  3. Android -> 服务器:代码

其他时间:

  1. Android -> 服务器:id token
  2. 服务器 - 我有代码,可以调用电话。

对于网络,您可以使用相同的流程或每次都继续发送代码,但如果响应包含刷新 token ,您仍应将刷新 token 保留在数据库中。

关于android - Google+登录跨客户端(android/web)身份验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28850836/

相关文章:

Android : Method onPictureTaken never called, 即使使用 AsyncTask

android - 可以连接多个 iBeacon

php - 谷歌分析 API 关键字

android - 在对 Python 的 Android 后端调用中验证 Id token

android - 运行时位置权限 - GoogleApiClient 尚未连接

android - 以编程方式裁剪 Android 屏幕抓取?

android - 大量调用 ViewRoot.draw()

php - 将过滤器与 Google Analytics 报告 API 结合使用

php - yii2、谷歌 oauth2 和范围

php - (400) 使用 Google API PHP 客户端和 Admin SDK 的错误请求