Google OpenIDConnect:为什么我没有得到 'openid_id' 值和 'sub'?
Google OpenIDConnect: Why am I not getting an 'openid_id' value along with 'sub'?
我已经阅读了我能找到的关于从 Google OpenID 2
迁移到 OAuth 2/OpenIDConnect
的所有文档,并且我目前正在使用来自 phpclasses.org 的一个不错的 class。这个 class 似乎在 Google 和 Facebook 上都能很好地工作(还没有尝试过其他提供商),但是我在 Google 的迁移的一个方面遇到了问题对我来说非常关键的路径:除了该用户的新 OpenIDConnect 'sub' 值之外,还获取 google 用户的旧 OpenID 标识符。我让用户仅通过他们的旧 OpenID 标识符在我的数据库中注册。
根据 Google 的 Migration Guide 中的第 3 步,看起来我需要做的就是向发送到 [=16= 的身份验证请求添加一个参数 "openid.realm=http://www.example.com"
].
我在我的旧代码中查找了我用于其 OpenID 注册过程的领域(它是 'http://' . $_SERVER['HTTP_HOST'];
),然后我确保我的重定向 urls应用程序与该领域兼容。
我将该值(url 编码)添加为 openid.realm
参数的值,该参数传递给 class 中的身份验证请求。但是当 class 将令牌交换为访问令牌时,它返回了正确的电子邮件、名称、子等,但没有 openid_id
参数。顺便说一句,我的作用域参数是 'openid email profile'
有人建议我还应该尝试什么,或者我可以做什么来确定问题出在哪里?有没有人有在php代码中获取openid_id
参数值的成功经验?我真的不想使用他们的 "Sign-in with Google" 按钮去客户端路由,并且根据文档,这真的不是必需的(而且没有特别的理由相信如果我这样做它会解决我的问题).
刚刚发现当您将 authorization_code 换成 access_token 时,它在 id_token 中与 access_token 一起返回。
在Migration Document中,第3步前两段:
When you send an OpenID Connect authentication request URI to Google
as described in Step 1, you include an openid.realm parameter. The
response that is sent to your redirect_uri includes an authorization
code that your application can use to retrieve an access token and an
ID token. (You can also retrieve an ID token directly from the OpenID
Connect authentication request by adding id_token to the response_type
parameter, potentially saving a back-end call to the token endpoint.)
The response from that token request includes the usual fields
(access_token, etc.), plus an openid_id field and the standard OpenID
Connect sub field. The fields you need in this context are openid_id
and sub:
这令人困惑并且 misleading/wrong。什么令牌请求?身份验证请求 returns 一个授权代码,您可以将其交换为 access_token
和 id_token
。关于将 id_token
添加到 response_type 的括号注释并没有多大帮助,因为我尝试这样做的各种方法都导致了错误。但无论如何,
"usual fields (access_token, etc.), plus an openid_id field..."
错了。 access_token
永远不会出现在 openid_id
字段的同一列表中。 access_token
与 id_token
一起出现在列表中,openid_id
字段在 id_token
!
中编码
出于测试目的,您可以使用 https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=<string>
解码 id_token
在本文档中,我找不到有关如何解码 id_token
的有用说明,仅说明它们是敏感的,以及如何验证它们(尽管如果直接从google 端点就像这里的情况一样)。我下载了 google 的 php client,并从中提取了代码(src/Google/Auth/OAuth2.php
和 src/Google/Utils.php
)。从那里很容易弄清楚如何解码 id_token
字符串:在 .
、base64_decode
元素 1 和 [=30 上展开=]那个。
2015-05-21 更新: 回复@Arthur 的 "answer",作为对此答案的评论更合适。我自己会评论那个答案,但评论不能太长,也不允许上传图片,而且我认为这个额外的信息可以改善我的答案...
下面是 netbeans/xdebug 的屏幕截图,显示了我在解码 id_token 时得到的数组元素。有趣的是,此处列出的字段与@Arthur 列出的字段的交集是空集。所以我怀疑无论@Arthur 正在解码什么,它都不是此处描述的那种 id_token。我对这些东西还不够熟悉,甚至猜不出那个答案中正在解码的是什么。
恐怕我没有时间去挖掘我用来提取生成 id_token 的确切代码路径的库,我使用我描述的简单算法解码得到这个数组。但是我可以告诉你,我使用的库是这样的:http://www.phpclasses.org/package/7700-PHP-Authorize-and-access-APIs-using-OAuth.html
按照记录使用它并不能为您提供所需的 id_token,原因有二:
使用 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"
},
就在调用Initialize()
之后,需要添加
$client->store_access_token_response = true;
否则,将无法访问实际的 access_token 响应(至少不是我使用 class 的方式)。完成这两项更改后,我使用此 class 获取 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...
}
尽管有 sootsnoot(自己)的回答,但我仍然无法在任何地方找到 openid_id 字段。解码 id_token 时只有 "issuer", "issued_to", "audience", "user_id" , "expires_in" , "issued_at", "email" 和 "nonce" 字段。
看不到 "openid_id" 个字段..
有什么想法吗?
作为对 sootsnoot 的回应的回应 :) 对于没有足够的声誉发表评论,我深表歉意,否则我会这样做的。
我正在使用从自动配置中获取端点的 OpenID Connect 库:https://accounts.google.com/.well-known/openid-configuration
因此,假设端点不是问题。确实我好像查错了id_token。但是,即使检查正确的,我仍然看不到 "openid_id" 字段。我现在看到了你所有的东西,除了我有一个 "nonce" 字段而不是 "openid_id" 字段:
stdClass::__set_state(数组( 'iss' => 'https://accounts.google.com', 'sub' => ****, 'azp' => ****, 'email' => ****, 'nonce' => ****, 'at_hash' => ****, 'email_verified' => 真, 'aud' => ****, 'iat' => ****, 'exp' => 1432300788, ))
一定是哪里做错了,但是...
最终更新:
发现问题:将领域参数作为 openid_realm=... 而不是 openid.realm=...
传递
哦,我觉得我很愚蠢... :)
我已经阅读了我能找到的关于从 Google OpenID 2
迁移到 OAuth 2/OpenIDConnect
的所有文档,并且我目前正在使用来自 phpclasses.org 的一个不错的 class。这个 class 似乎在 Google 和 Facebook 上都能很好地工作(还没有尝试过其他提供商),但是我在 Google 的迁移的一个方面遇到了问题对我来说非常关键的路径:除了该用户的新 OpenIDConnect 'sub' 值之外,还获取 google 用户的旧 OpenID 标识符。我让用户仅通过他们的旧 OpenID 标识符在我的数据库中注册。
根据 Google 的 Migration Guide 中的第 3 步,看起来我需要做的就是向发送到 [=16= 的身份验证请求添加一个参数 "openid.realm=http://www.example.com"
].
我在我的旧代码中查找了我用于其 OpenID 注册过程的领域(它是 'http://' . $_SERVER['HTTP_HOST'];
),然后我确保我的重定向 urls应用程序与该领域兼容。
我将该值(url 编码)添加为 openid.realm
参数的值,该参数传递给 class 中的身份验证请求。但是当 class 将令牌交换为访问令牌时,它返回了正确的电子邮件、名称、子等,但没有 openid_id
参数。顺便说一句,我的作用域参数是 'openid email profile'
有人建议我还应该尝试什么,或者我可以做什么来确定问题出在哪里?有没有人有在php代码中获取openid_id
参数值的成功经验?我真的不想使用他们的 "Sign-in with Google" 按钮去客户端路由,并且根据文档,这真的不是必需的(而且没有特别的理由相信如果我这样做它会解决我的问题).
刚刚发现当您将 authorization_code 换成 access_token 时,它在 id_token 中与 access_token 一起返回。
在Migration Document中,第3步前两段:
When you send an OpenID Connect authentication request URI to Google as described in Step 1, you include an openid.realm parameter. The response that is sent to your redirect_uri includes an authorization code that your application can use to retrieve an access token and an ID token. (You can also retrieve an ID token directly from the OpenID Connect authentication request by adding id_token to the response_type parameter, potentially saving a back-end call to the token endpoint.)
The response from that token request includes the usual fields (access_token, etc.), plus an openid_id field and the standard OpenID Connect sub field. The fields you need in this context are openid_id and sub:
这令人困惑并且 misleading/wrong。什么令牌请求?身份验证请求 returns 一个授权代码,您可以将其交换为 access_token
和 id_token
。关于将 id_token
添加到 response_type 的括号注释并没有多大帮助,因为我尝试这样做的各种方法都导致了错误。但无论如何,
"usual fields (access_token, etc.), plus an openid_id field..."
错了。 access_token
永远不会出现在 openid_id
字段的同一列表中。 access_token
与 id_token
一起出现在列表中,openid_id
字段在 id_token
!
出于测试目的,您可以使用 https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=<string>
id_token
在本文档中,我找不到有关如何解码 id_token
的有用说明,仅说明它们是敏感的,以及如何验证它们(尽管如果直接从google 端点就像这里的情况一样)。我下载了 google 的 php client,并从中提取了代码(src/Google/Auth/OAuth2.php
和 src/Google/Utils.php
)。从那里很容易弄清楚如何解码 id_token
字符串:在 .
、base64_decode
元素 1 和 [=30 上展开=]那个。
2015-05-21 更新: 回复@Arthur 的 "answer",作为对此答案的评论更合适。我自己会评论那个答案,但评论不能太长,也不允许上传图片,而且我认为这个额外的信息可以改善我的答案...
下面是 netbeans/xdebug 的屏幕截图,显示了我在解码 id_token 时得到的数组元素。有趣的是,此处列出的字段与@Arthur 列出的字段的交集是空集。所以我怀疑无论@Arthur 正在解码什么,它都不是此处描述的那种 id_token。我对这些东西还不够熟悉,甚至猜不出那个答案中正在解码的是什么。
恐怕我没有时间去挖掘我用来提取生成 id_token 的确切代码路径的库,我使用我描述的简单算法解码得到这个数组。但是我可以告诉你,我使用的库是这样的:http://www.phpclasses.org/package/7700-PHP-Authorize-and-access-APIs-using-OAuth.html
按照记录使用它并不能为您提供所需的 id_token,原因有二:
使用 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" },
就在调用
Initialize()
之后,需要添加$client->store_access_token_response = true;
否则,将无法访问实际的 access_token 响应(至少不是我使用 class 的方式)。完成这两项更改后,我使用此 class 获取 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... }
尽管有 sootsnoot(自己)的回答,但我仍然无法在任何地方找到 openid_id 字段。解码 id_token 时只有 "issuer", "issued_to", "audience", "user_id" , "expires_in" , "issued_at", "email" 和 "nonce" 字段。 看不到 "openid_id" 个字段..
有什么想法吗?
作为对 sootsnoot 的回应的回应 :) 对于没有足够的声誉发表评论,我深表歉意,否则我会这样做的。
我正在使用从自动配置中获取端点的 OpenID Connect 库:https://accounts.google.com/.well-known/openid-configuration 因此,假设端点不是问题。确实我好像查错了id_token。但是,即使检查正确的,我仍然看不到 "openid_id" 字段。我现在看到了你所有的东西,除了我有一个 "nonce" 字段而不是 "openid_id" 字段:
stdClass::__set_state(数组( 'iss' => 'https://accounts.google.com', 'sub' => ****, 'azp' => ****, 'email' => ****, 'nonce' => ****, 'at_hash' => ****, 'email_verified' => 真, 'aud' => ****, 'iat' => ****, 'exp' => 1432300788, ))
一定是哪里做错了,但是...
最终更新:
发现问题:将领域参数作为 openid_realm=... 而不是 openid.realm=...
传递哦,我觉得我很愚蠢... :)