如何识别谷歌OAuth2用户?

73

我使用Facebook登录来识别用户。当有新用户注册时,我会将他们的userID 存储在我的数据库中。下次他们访问时,我便能通过识别他们的Facebook ID 来知道是哪个用户。

现在我想使用 Google 的OAuth2 来实现同样的功能,但是如何识别用户呢?

Google 向我发送了几个 codes 和 tokens(access_token、id_token、refresh_token),不过它们都是变化的。也就是说,如果我在注销后2分钟内重新登录,这3个值都已发生变化。如何才能唯一地识别用户呢?

我正在使用他们的 PHP 客户端库:https://code.google.com/p/google-api-php-client/

6个回答

105

就像其他人提到的那样,您可以使用刚收到的OAuth2令牌向https://www.googleapis.com/oauth2/v3/userinfo发送GET请求,然后您将获得有关用户的一些信息(例如id、名称等)的响应。

值得一提的是,Google实现了OpenID Connect,而这个用户信息终点只是其中的一部分。

OpenID Connect是在OAuth2之上的认证层。当在Google的令牌终点交换授权code时,您会获得一个访问令牌(access_token参数)和一个OpenID Connect ID令牌(id_token参数)。

这些令牌都是JWT(JSON Web Token,https://datatracker.ietf.org/doc/html/draft-ietf-oauth-json-web-token)。

如果您对它们进行解码,您将会获得一些声明,包括用户的id。如果您将此ID链接到您的数据库中的用户,您可以立即识别他们而无需进行额外的userinfo GET请求(节省时间)。

如评论中所述,这些令牌使用Google的私钥进行签名,因此您可能希望使用Google的公钥(https://www.googleapis.com/oauth2/v3/certs)验证签名以确保它们是真实的。

您可以通过将其粘贴到https://jwt.io/中查看JWT的内容(向下滚动以获取JWT调试器)。声明看起来像:

{
    "iss":"accounts.google.com",
    "id":"1625346125341653",
    "cid":"8932346534566-hoaf42fgdfgie1lm5nnl5675g7f167ovk8.apps.googleusercontent.com",
    "aud":"8932346534566-hoaf42fgdfgie1lm5nnl5675g7f167ovk8.apps.googleusercontent.com",
    "token_hash":"WQfLjdG1mDJHgJutmkjhKDCdA",
    "iat":1567923785,
    "exp":1350926995
}

还有各种编程语言的库可以用于编写程序来解码JWT。

PS:为了获取Google的OpenID Connect提供程序支持的最新URL和功能列表,您可以检查此URL:https://accounts.google.com/.well-known/openid-configuration


1
是的,但为了安全地使用此信息,您必须验证JWT的签名,而这需要公钥。您知道谷歌在哪里提供他们的公钥吗? - jazmit
7
好的,找到了:https://www.googleapis.com/oauth2/v1/certs 我还应该指出,进行验证绝对是必要的,否则攻击者可以轻易地使用任何已经注册的Google账户登录到您的应用程序。 - jazmit
12
我不认为这完全准确。access_token 不是一个 JWT token。 - JoseOlcese
1
如果您不想将您的加密令牌粘贴到某个随机网站上,那么您也可以通过 tr '._-' '\n/+' | sed '2s|$|===|p;d' | base64 -D 将 ID 令牌传输。在 sed 中的 2 选择元组的第二部分,这可能是您想要的。 - phs
1
@phs jwt.io 不是一个“随意”的网站,它由Auth0维护,Auth0是JWT标准的创建者之一。 - dan
显示剩余3条评论

33

我将这个方法插入到了google-api-php-client/src/apiClient.php中:

public function getUserInfo() 
{
    $req = new apiHttpRequest('https://www.googleapis.com/oauth2/v1/userinfo');
    // XXX error handling missing, this is just a rough draft
    $req = $this->auth->sign($req);
    $resp = $this->io->makeRequest($req)->getResponseBody();
    return json_decode($resp, 1);  
}

现在我可以调用:

$client->setAccessToken($_SESSION[ 'token' ]);
$userinfo = $client->getUserInfo();
它返回一个像这样的数组(如果请求了该范围,则还包括电子邮件):
Array
(
    [id] => 1045636599999999999
    [name] => Tim Strehle
    [given_name] => Tim
    [family_name] => Strehle
    [locale] => de
)

这个解决方案最初来源于该线程:https://groups.google.com/forum/#!msg/google-api-php-client/o1BRsQ9NvUQ/xa532MxegFIJ


9
请注意,Google 最近更改了响应方式,id_token 中静态标识符现在以键 sub 的形式而非之前及上述示例中的键 id 存在。据我所知,这个变化是他们对 OpenID Connect 协议的解释。不幸的是,在我撰写此文时,响应似乎有些随机:有时是 id,有时是 sub,因此我需要支持两种情况。 - Mikko Rantalainen
我认为/oauth2/v1/userinfo会给你id,而/oauth2/v3/userinfo会给你sub(注意不同的版本!)如果您没有提供版本,可能会更改。 - gengkev

22

2
我不确定这是否正确。尽管文档中如此说明,但如果我重启我的身份验证服务器、清除缓存并重新登录,那么我的 'sub' 值将会不同。 - JsAndDotNet
有其他人遇到HockeyJ的问题,即“sub”对于相同的登录会发生更改吗? - dardawk
是的,我也有“sub”不同的问题。刚在另一个帖子中提出了这个问题 https://stackoverflow.com/questions/53421907/can-google-user-id-be-changed - Ilya Dzivinskyi

2
"

“Who is this?”本质上是一个服务;您需要作为范围请求访问它,然后向Google个人资料资源服务器发出请求以获取身份。有关详细信息,请参见OAuth 2.0登录

"

这似乎是最技术上正确的,但它缺少当前被接受答案所具有的额外细节。将两个答案合并在一起,你就会得到黄金。 - Emmaly

1
虽然JWT可以使用公钥在本地进行验证,(Google API客户端库会自动下载和缓存公钥),但通过https://www.googleapis.com/oauth2/v1/tokeninfo端点在Google的侧面检查令牌是必要的,以检查自令牌创建以来应用程序的访问是否已被撤销。

0

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