如何验证Google身份验证API访问令牌?

190

如何验证Google身份验证访问令牌?

我需要以某种方式查询Google并询问:[给定的访问令牌]是否适用于[example@example.com] Google帐户?

简短版本

通过Google身份验证API :: Web应用程序的OAuth身份验证提供的访问令牌可以用于从一系列Google服务请求数据。但是,如何检查给定访问令牌是否适用于给定的Google帐户尚不清楚。我想知道如何做到这一点。

详细版本

我正在开发一个使用基于令牌的身份验证的API。在提供有效的用户名+密码或从任意一个可验证的第三方服务中提供第三方令牌之后,将返回一个令牌。

其中之一的第三方服务将是Google,允许用户使用其Google帐户对我的服务进行身份验证。稍后,这将扩展到包括Yahoo帐户、可信OpenID提供商等。

基于Google的访问示例:

alt text

'API'实体完全由我控制。'公共接口'实体是任何基于Web或桌面的应用程序。有些公共接口在我控制之下,其他的则不在我的控制之下,还有一些我可能永远都不会知道。

因此,在步骤3中,我无法信任提供给API的令牌。这将与相应的Google帐户电子邮件地址一起提供。

我需要以某种方式查询Google并询问:这个访问令牌是否适用于example@example.com

在这种情况下,example@example.com是Google帐户的唯一标识符 - 某人用来登录其Google帐户的电子邮件地址。不能假设这是一个Gmail地址 - 某人可以拥有一个Google帐户而没有Gmail帐户。

谷歌文档清晰地说明了如何使用访问令牌从多个谷歌服务中检索数据。但似乎没有说明如何首先检查给定的访问令牌是否有效。

更新 该令牌对 N 个谷歌服务有效。我无法尝试针对谷歌服务验证令牌,因为我不知道一个特定用户实际使用的所有谷歌服务的子集。

此外,我永远不会使用谷歌认证访问令牌来访问任何谷歌服务,仅仅作为验证所谓谷歌用户实际上就是他们所说的那个人的手段。如果有其他方法可以做到这一点,我很乐意尝试。


这个问题是关于哪种特定的身份验证服务(OAuth、AuthSub、已安装的应用程序等)?请提供更详细的链接。 - Martin v. Löwis
@Martin v. Löwis:'Web应用程序的OAuth身份验证'服务-我已更新问题的开头以反映这一点。谢谢你指出这个问题! - Jon Cram
有趣的一篇关于 Google 钥匙验证的文章,或许可以更深入地了解一下:http://groups.google.com/group/Google-Maps-API/msg/f9e3c5ad3cbda4d7 - dotjoe
请注意:即使没有https,图像URL也是404。 - General Grievance
12个回答

196

进行用户检查,只需发布获取访问令牌作为accessToken并发布它,然后获取响应。

https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=accessToken
你也可以在浏览器的地址栏中尝试,同时在Java中使用HttpPost和Response。响应将会是这样的:
{
     "issued_to": "xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
     "audience": "xxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
     "user_id": "xxxxxxxxxxxxxxxxxxxxxxx",
     "scope": "https://www.googleapis.com/auth/userinfo.profile https://gdata.youtube.com",
     "expires_in": 3340,
     "access_type": "offline"
    }

范围是accessToken所授予的权限。您可以在此链接中检查范围IDs。

更新: 新API如下发布

https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123

响应将会是

 {
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile" and
 // "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}

了解更多信息,请访问https://developers.google.com/identity/sign-in/android/backend-auth


17
Google的OAuth2有更新版本 - v3。在此处查看示例:https://developers.google.com/identity/sign-in/android/backend-auth - AlikElzin-kilaka
14
"由于请求可能会被限流或出现间歇性错误,因此它不适合用于生产代码。" https://developers.google.com/identity/sign-in/ios/backend-auth#calling-the-tokeninfo-endpoint。Remco的答案是正确的。 - Heinzlmaen
使用更新的API,我能够检索用户的Google ID吗? - Gustavo Shigueo

111

大多数回答都是正确的,但不够准确。JWT 的思想在于您可以验证令牌而无需每次联系发行者。您必须检查令牌的 ID 并使用谷歌用于签署令牌的已知公钥验证令牌的签名。

请参见下一篇文章,了解如何执行此操作。

http://ncona.com/2015/02/consuming-a-google-id-token-from-a-server/


39
请点赞!JWT 的想法是可以在不必每次联系发行方的情况下验证令牌。 - Moritz Schmitz v. Hülst
3
是的!如果他们只是调用Google获取令牌信息,那么人们正在对Google进行DDoS攻击。 - Dee
18
因为谷歌访问令牌不是JWT,所以您无法使用它来完成此操作。请参阅https://dev59.com/dFYM5IYBdhLWcg3wmxCl。 - DanielJaramillo
1
@DanielJaramillo 感谢您的见解。id 部分实际上是一个可验证的 jwt。观点仍然成立。离线验证是这里的主要思想。 - Remco
4
谢谢!文档说明:“由于请求可能会被限制或出现间歇性错误,因此不适合在生产代码中使用。” - Samuel Rowe
1
@Remco,你有关于访问令牌中的“id部分”以及如何离线验证它的任何指针吗?在外面找不到任何相关信息。 - Nigini

46

后端文档 - 文档源在这里 - eton_ceb

21
请使用以下终端点获取用户信息,例如名称、电子邮件、照片等。
https://www.googleapis.com/oauth2/v3/userinfo?access_token=<access token>
使用以下端点获取令牌信息,例如到期时间、令牌范围等。
https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=<access token>

18
function authenticate_google_OAuthtoken($user_id)
{
    $access_token   = google_get_user_token($user_id); // get existing token from DB
    $redirecturl    = $Google_Permissions->redirecturl;
    $client_id      = $Google_Permissions->client_id;
    $client_secret  = $Google_Permissions->client_secret;
    $redirect_uri   = $Google_Permissions->redirect_uri;
    $max_results    = $Google_Permissions->max_results;

    $url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token='.$access_token;
    $response_contacts  =  curl_get_responce_contents($url);
    $response   =   (json_decode($response_contacts));

    if(isset($response->issued_to))
    {
        return true;
    }
    else if(isset($response->error))
    {
        return false;
    }
}

2
这个答案几乎仍然有效。Issued_to似乎不再设置了。https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken - frostymarvelous

12
  1. 根据 Google 的文档,建议使用 Google 的 API 客户端库来进行令牌验证、声明提取等操作。其比起编写自己的自定义代码要简单得多。
  2. 从性能角度考虑,应该在本地解析令牌,而不是再次调用 Google。当然,需要使用 Google 的公钥,并且在上述 #1 的 Google 客户端库中实现了缓存策略。
  3. 仅供参考。Google 也使用 JWT 令牌。请参考下面的图片。

enter image description here


这个答案值得被采纳。所提出的方法在每次验证时所需的代码和消耗的CPU/网络资源方面都非常高效。与其他建议使用自制代码的答案相比,它也不太可能引入安全漏洞。 - Diomidis Spinellis

11
除了access_token之外,Google oauth代码流响应还返回包含加密形式的验证信息的id_token
引用块中提到, ID token有用的一点是您可以将其传递给应用程序的不同组件。 这些组件可以使用ID token作为轻量级身份验证机制,用于验证应用程序和用户。 但是,在使用ID token中的信息或将其视为用户已通过身份验证的声明之前,必须对其进行验证。
验证ID token需要执行以下几个步骤:
  • 验证ID token是经过适当Google公钥签名的JWT。
  • 验证ID token中aud的值是否等于您应用程序的客户端ID。
  • 验证ID token中iss的值是否等于accounts.google.com或https://accounts.google.com
  • 验证ID token的到期时间(exp)是否已过期。
  • 如果在请求中传递了hd参数,请验证ID token是否具有与您的Google Apps托管域匹配的hd声明。
链接 https://developers.google.com/identity/protocols/OpenIDConnect#validatinganidtoken 提供了验证ID token的代码示例。
另请参见 https://security.stackexchange.com/questions/37818/why-use-openid-connect-instead-of-plain-oauth

1

这里有一个使用Guzzle的例子:

/**
 * @param string $accessToken JSON-encoded access token as returned by \Google_Client->getAccessToken() or raw access token
 * @return array|false False if token is invalid or array in the form
 * 
 * array (
 *   'issued_to' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
 *   'audience' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
 *   'scope' => 'https://www.googleapis.com/auth/calendar',
 *   'expires_in' => 3350,
 *   'access_type' => 'offline',
 * )
 */
public static function tokenInfo($accessToken) {
    if(!strlen($accessToken)) {
        return false;
    }

    if($accessToken[0] === '{') {
        $accessToken = json_decode($accessToken)->access_token;
    }

    $guzzle = new \GuzzleHttp\Client();

    try {
        $resp = $guzzle->get('https://www.googleapis.com/oauth2/v1/tokeninfo', [
            'query' => ['access_token' => $accessToken],
        ]);
    } catch(ClientException $ex) {
        return false;
    }

    return $resp->json();
}

0
我需要以某种方式查询Google并询问:对于example@example.com,此访问令牌是否有效?
不需要。您只需要从您的API域请求{{link1:针对Google帐户用户的联合登录}}的标准登录即可。之后,您才能将“持久用户ID”与“公共接口”中拥有的进行比较。
realm值在Google联合登录页面上用于向用户识别请求站点。它还用于确定由Google返回的持久用户ID的值。
因此,您需要与“公共接口”处于同一个域中。
而且不要忘记用户需要确信您的API是可信的;) 因此,Google会询问用户是否允许您检查其身份。

0

任意的OAuth访问令牌不能用于身份验证,因为令牌的含义超出了OAuth核心规范。它可能是为单次使用或狭窄的过期时间而设计的,或者它可能提供了用户不想提供的访问权限。此外,它是不透明的,获得它的OAuth消费者可能从未看到任何类型的用户标识符。

OAuth服务提供商和一个或多个消费者可以轻松地使用OAuth提供可验证的身份验证令牌,并且有一些提议和想法来实现这一点,但是一个仅使用OAuth核心的任意服务提供商不能在没有与消费者的其他协调的情况下提供此功能。Google特定的AuthSubTokenInfo REST方法以及用户的标识符很接近,但也不合适,因为它可能会使令牌无效,或者令牌可能已过期。

如果您的Google ID是OpenId标识符,并且您的“公共界面”是Web应用程序或可以调用用户的浏览器,则应该使用Google的OpenID OP。

OpenID只需要将用户发送到OP并获取签名断言即可。交互仅为RP的利益而进行。没有长期存在的令牌或其他用户特定的句柄可用于指示RP已成功通过OP对用户进行身份验证。

验证以前的OpenID标识符身份认证的一种方法是再次执行身份认证,假设使用相同的用户代理。OP应该能够在不需要用户交互的情况下返回积极的断言(例如通过验证Cookie或客户端证书)。OP可以自由地要求另一个用户交互,并且如果认证请求来自另一个域,则可能会这样做(我的OP为我提供了在未来无需交互重新认证此特定RP的选项)。而且在Google的情况下,用户获取OAuth令牌所经历的UI可能不使用相同的会话标识符,因此用户将需要重新进行身份认证。但无论如何,您都将能够声明身份。


OpenID 2.0最近已被Google废弃和禁用,转而支持基于OAuth的OpenID Connect,该协议提供了可验证的身份标识令牌。 - Vadzim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接