Google OpenIDConnect:为什么我没有获得'openid_id'值以及'sub'?

时间:2015-04-19 05:15:15

标签: google-oauth google-openid

我已经阅读了从Google OpenID 2迁移到OAuth 2/OpenIDConnect时可以找到的所有文档,目前我正在使用phpclasses.org中的一个很好的类。这个类似乎与谷歌和Facebook都很合作(还没有尝试过其他供应商),但我对谷歌迁移路径的一个方面存在问题,这对于我:获取谷歌用户的旧OpenID标识符以及新的OpenIDConnect' sub'该用户的价值。我只是通过旧的OpenID标识符在我的数据库中注册了用户。

根据Google Migration Guide中的第3步,看起来我需要做的就是向发送到"openid.realm=http://www.example.com"的身份验证请求添加参数https://accounts.google.com/o/oauth2/auth

我在我的旧代码中查找了我用于其OpenID注册过程的域(它是'http://' . $_SERVER['HTTP_HOST'];),然后我确保我的应用程序中的重定向URL与该域兼容。

我将该值(url-encoded)添加为在类中生成的身份验证请求上传递的openid.realm参数的值。但是当类交换令牌以获取访问令牌时,它会返回正确的电子邮件,名称,子等,但是没有openid_id参数存在。顺便说一下,我的范围参数是'openid email profile'

有没有人建议我应该尝试什么,或者我可以做些什么来确定问题是什么?有没有人在PHP代码中获得openid_id参数值的成功经验?我真的不愿意使用他们的#34;登录Google"而不是客户端路线。按钮,并根据真正不应该有必要的文档(如果我这样做的话,没有特别的理由相信它会解决我的问题)。

2 个答案:

答案 0 :(得分:0)

当你交换access_token的authorization_code时,刚刚发现它与id_token一起返回了access_token。

Migration Document,第3步前两段:

  

向Google发送OpenID Connect身份验证请求URI时   如步骤1中所述,您包含一个openid.realm参数。该   发送到您的redirect_uri的响应包括授权   您的应用程序可用于检索访问令牌的代码   ID令牌。 (您还可以直接从OpenID检索ID令牌   通过将id_token添加到response_type来连接身份验证请求   参数,可能保存对令牌端点的后端调用。)

     

来自该令牌请求的响应包括通常的字段   (access_token等),加上openid_id字段和标准OpenID   连接子字段。您在此上下文中需要的字段是openid_id   和子:

这令人困惑和误导/错误。什么令牌请求?身份验证请求会返回授权代码,您可以为access_tokenid_token进行交换。关于将id_token添加到response_type的括号内容并没有多大帮助,因为我尝试这样做的各种方法导致了错误。但无论如何,

  

“常用字段(access_token等),加上openid_id字段......”

错了。 access_token永远不会出现在openid_id字段的同一列表中。 access_token显示在包含id_token的列表中,openid_id字段在id_token内编码!

出于测试目的,您可以使用id_token

解码https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=<string>

在本文档中,我找不到有关如何解码id_token的有用说明,只是对其敏感性的警告,以及如何验证它们(尽管如果直接从谷歌端点获取验证则不需要就像这里的情况一样)。我下载了谷歌的php client,并从中提取了代码(src/Google/Auth/OAuth2.phpsrc/Google/Utils.php)。从中可以很容易地弄清楚如何解码id_token字符串:在 . base64_decode元素1和json_decode上爆炸

更新2015-05-21:回复@Arthur的“回答”,这对这个答案的评论更合适。我本人会对这个答案发表评论,但评论不允许很长,不允许图片上传,而且我认为这些额外信息可以改善我的答案...

下面是netbeans / xdebug的截图,显示了解码id_token时得到的数组元素。有趣的是,此处列出的字段与@Arthur列出的字段的交集是空集。所以我怀疑无论@Arthur正在解码,它都不是这里描述的那种id_token。我对这些东西还不够熟悉,甚至不知道在那个答案中被解码的是什么。

The elements I see in a decoded id_token

我担心我没有时间挖掘我用来提取产生id_token的确切代码路径的库,我使用我描述的简单算法解码得到这个数组。但我可以告诉你,我使用的库是:http://www.phpclasses.org/package/7700-PHP-Authorize-and-access-APIs-using-OAuth.html

正如文档所述使用它并没有为您提供所需的id_token,原因有两个:

  1. 使用Oauth 2的Google预配置服务器不处理openid.realm参数。为了解决这个问题,我将以下服务器定义添加到oauth_configuration.json文件中:

    "Google-OpenIdConnect":
    {
            "oauth_version": "2.0",
            "dialog_url": "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={STATE}&openid.realm={REALM}",
            "offline_dialog_url": "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={STATE}&access_type=offline&approval_prompt=force",
            "access_token_url": "https://accounts.google.com/o/oauth2/token"
    },
    
  2. 在致电Initialize()之后,您需要添加

    $client->store_access_token_response = true;
    

    没有它,实际的access_token响应是不可访问的(至少不是我使用该类的方式)。有了这两个更改,我使用此类获取openid_id的确切代码如下:

    protected function jwt_decode($jwt) {
        $segments = explode(".", $jwt);
        if (count($segments) != 3) {
            throw new Exception("Wrong number of segments in token: $jwt");
        }
        // Parse envelope.
        $envelope = json_decode($this->urlSafeB64Decode($segments[0]), true);
        if (!$envelope) {
            throw new Exception("Can't parse token envelope: " . $segments[0]);
        }
        // Parse token
        $json_body = $this->urlSafeB64Decode($segments[1]);
        $payload = json_decode($json_body, true);
        return $payload;
    }
    
    protected function getOpenid_id() {
        require_once 'Phpclasses/Http/Class.php';
        require_once 'Phpclasses/OauthClient/Class.php';
        require 'Phpclasses/Google/private/keys.php';
        $client = new oauth_client_class;
        $client->configuration_file = $phpclasses_oauth_dir . '/oauth_configuration.json';
        $client->server = 'Google-OpenIdConnect';
        $client->redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . strtok($_SERVER['REQUEST_URI'], '?');
        $client->client_id = $GOOGLE_APPID;
        $client->client_secret = $GOOGLE_APPSECRET;
    
        $client->scope = 'openid email';
        $client->realm = $this->getRequest()->getScheme() . '://' . $this->getRequest()->getHttpHost();
        $me = null;
        if (($success = $client->Initialize())) {
            // set *after* the call to Initialize
            $client->store_access_token_response = true;
            if (($success = $client->Process())) {
                if (strlen($client->authorization_error)) {
                    $client->error = $client->authorization_error;
                    $success = false;
                }
                elseif (strlen($client->access_token)) {
                    $success = $client->CallAPI('https://www.googleapis.com/oauth2/v1/userinfo', 'GET', array(), array('FailOnAccessError' => true), $user);
                    $me = (array) $user;
                    if (!array_key_exists('id_token', $client->access_token_response)) {
                        throw new Exception('No id_token in \$client->access_token_response');
                    }
                    $openid_id = $this->jwt_decode($client->access_token_response['id_token']);
                    $me['openid_id'] = $openid_id;
                }
            }
            $success = $client->Finalize($success);
        }
        if ($client->exit)
            exit;
        $client->ResetAccessToken();
        if ($success) {
            return $me;
        }
        // Code to handle failure...
    }
    

答案 1 :(得分:0)

尽管有sootsnoot(自己)的答案,我仍然无法在任何地方找到openid_id字段。当解码id_token时,只有&#34;发行者&#34;,&#34; issued_to&#34;,&#34;观众&#34;,&#34; user_id&#34; ,&#34; expires_in&#34; ,&#34; issued_at&#34;,&#34; email&#34;和&#34; nonce&#34;领域。 不&#34; openid_id&#34;看见的领域..

有什么想法吗?

回应sootsnoot的回复:)我为没有足够的声誉发表评论而道歉,否则就会这样做。

使用从自动配置获取端点的OpenID Connect库:https://accounts.google.com/.well-known/openid-configuration 所以假设端点不是问题。事实上,我似乎正在检查错误的id_token。但是,即使检查正确的,我仍然没有看到&#34; openid_id&#34;领域。我现在看到你拥有的一切,除了我有一个&#34; nonce&#34;字段而不是&#34; openid_id&#34;字段:

stdClass :: __ set_state(array(&#39; iss&#39; =&gt;&#39; https://accounts.google.com&#39;,&#39; sub&#39; =&gt; **** ,&#39; azp&#39; =&gt; ****,&#39;电子邮件&#39; =&gt; ****,&#39; nonce&#39; =&gt; ****,& #39; at_hash&#39; =&gt; ****,&#39; email_verified&#39; =&gt; true,&#39; aud&#39; =&gt; ****,&#39; iat&# 39; =&gt; ****,&#39; exp&#39; =&gt; 1432300788,))

一定是做错了什么,但是......

最终更新:

发现问题:将realm参数传递为openid_realm = ...而不是openid.realm = ...

哦,我感到愚蠢......:)