使用 Google Identity 服务或 OAuth 2.0 授权代码流程时,Google 会使用 POST 方法将 ID 令牌返回到重定向端点。或者,OIDC 隐式流程使用 GET 请求。因此,您的应用负责将这些收到的凭据安全地传输到您的服务器。
这是隐式流程,ID 令牌在网址 fragment 中返回,客户端 JavaScript 必须解析该 fragment。您的应用负责实现自己的验证机制,以确保请求的真实性并防止 CSRF 等攻击。
HTTP/1.1 302 Found Location: https://<REDIRECT_URI>#access_token=<ACCESS_TOKEN>&token_type=bearer&expires_in=<TIME_IN_SECONDS>&scope=<SCOPE>&state=<STATE_STRING>
ID 令牌以 credential 字段的形式返回。在准备向服务器发送 ID 令牌时,GIS 库会自动将 g_csrf_token 添加到标头 Cookie 和请求正文中。以下是 POST 请求示例:
POST /auth/token-verification HTTP/1.1 Host: example.com Content-Type: application/json;charset=UTF-8 Cookie: g_csrf_token=<CSRF_TOKEN> Origin: https://example.com Content-Length: <LENGTH_OF_JSON_BODY> { "credential": "<ID_TOKEN>", "g_csrf_token": "<CSRF_TOKEN>", "client_id": "<CLIENT_ID>" }
验证
g_csrf_token以防止跨站请求伪造 (CSRF) 攻击:- 从
g_csrf_tokencookie 中提取 CSRF 令牌值。 - 从请求正文中提取 CSRF 令牌值。GIS 库会将此令牌作为参数(也称为
g_csrf_token)包含在 POST 请求正文中。 - 比较两个令牌值
- 如果这两个值都存在且完全一致,则表示相应请求是合法的,并且源自您的网域。
- 如果这些值不存在或不匹配,服务器必须拒绝相应请求。
此检查可确保请求是从您自己网域中运行的 JavaScript 发起的,因为只有您的网域可以访问
g_csrf_tokencookie。
- 从
验证 ID 令牌。
如需验证令牌是否有效,请确保满足以下条件:
- ID 令牌已由 Google 正确签名。使用 Google 的公钥(以 JWK 或 PEM 格式提供)验证令牌的签名。这些密钥会定期轮换;请检查响应中的
Cache-Control标头,以确定何时应再次检索这些密钥。 - ID 令牌中的
aud值等于您应用的某个客户端 ID。此检查是必要的,可防止向恶意应用发放的 ID 令牌被用于访问您应用后端服务器上有关同一用户的数据。 - ID 令牌中
iss的值等于accounts.google.com或https://accounts.google.com。 - ID 令牌的到期时间 (
exp) 尚未到期。 - 如果您需要验证 ID 令牌是否代表 Google Workspace 或 Cloud 组织账号,可以检查
hd声明,该声明表示用户的托管网域。如果需要将对资源的访问权限限制为仅限特定网域的成员,则必须使用此方法。如果缺少此声明,则表示相应账号不属于 Google 托管网域。
通过使用
email、email_verified和hd字段,您可以确定 Google 是否托管某个电子邮件地址并对其具有权威性。如果 Google 是权威方,则表示用户是合法的账号所有者,您可以跳过密码或其他身份验证方法。Google 具有权威性的情况:
email带有@gmail.com后缀,则表示这是 Gmail 账号。email_verified为 true 且设置了hd,则为 Google Workspace 账号。
用户可以注册 Google 账号,而无需使用 Gmail 或 Google Workspace。如果
email不包含@gmail.com后缀且hd不存在,则 Google 不具有权威性,建议使用密码或其他质询方法来验证用户身份。email_verified也可能为 true,因为 Google 最初在创建 Google 账号时验证了用户身份,但第三方电子邮件账号的所有权可能已发生变化。我们强烈建议您使用适用于您平台的 Google API 客户端库或通用 JWT 库,而不是自行编写代码来执行这些验证步骤。对于开发和调试,您可以调用我们的
tokeninfo验证端点。使用 Google API 客户端库
使用某个 Google API 客户端库(例如 Java、 Node.js、 PHP、 Python) 是在生产环境中验证 Google ID 令牌的推荐方法。
<ph type="x-smartling-placeholder"></ph> <ph type="x-smartling-placeholder"> </ph> 。 <ph type="x-smartling-placeholder">Java 要在 Java 中验证 ID 令牌,请使用 GoogleIdTokenVerifier 对象。例如:
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; ... GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory) // Specify the WEB_CLIENT_ID of the app that accesses the backend: .setAudience(Collections.singletonList(WEB_CLIENT_ID)) // Or, if multiple clients access the backend: //.setAudience(Arrays.asList(WEB_CLIENT_ID_1, WEB_CLIENT_ID_2, WEB_CLIENT_ID_3)) .build(); // (Receive idTokenString by HTTPS POST) GoogleIdToken idToken = verifier.verify(idTokenString); if (idToken != null) { Payload payload = idToken.getPayload(); // Print user identifier. This ID is unique to each Google Account, making it suitable for // use as a primary key during account lookup. Email is not a good choice because it can be // changed by the user. String userId = payload.getSubject(); System.out.println("User ID: " + userId); // Get profile information from payload String email = payload.getEmail(); boolean emailVerified = Boolean.valueOf(payload.getEmailVerified()); String name = (String) payload.get("name"); String pictureUrl = (String) payload.get("picture"); String locale = (String) payload.get("locale"); String familyName = (String) payload.get("family_name"); String givenName = (String) payload.get("given_name"); // Use or store profile information // ... } else { System.out.println("Invalid ID token."); }
GoogleIdTokenVerifier.verify()方法验证 JWT 签名、aud声明、iss声明以及exp项版权主张。如果您需要验证 ID 令牌是否代表 Google Workspace 或 Cloud 组织账号,您可以通过检查域名来验证
hd所有权声明 由Payload.getHostedDomain()方法返回。该email声明不足以保证账号是由网域管理 或组织。</ph> 。 <ph type="x-smartling-placeholder">Node.js 要在 Node.js 中验证 ID 令牌,请使用适用于 Node.js 的 Google Auth 库。 安装该库:
然后,调用npm install google-auth-library --save
verifyIdToken()函数。例如:const {OAuth2Client} = require('google-auth-library'); const client = new OAuth2Client(); async function verify() { const ticket = await client.verifyIdToken({ idToken: token, audience: WEB_CLIENT_ID, // Specify the WEB_CLIENT_ID of the app that accesses the backend // Or, if multiple clients access the backend: //[WEB_CLIENT_ID_1, WEB_CLIENT_ID_2, WEB_CLIENT_ID_3] }); const payload = ticket.getPayload(); // This ID is unique to each Google Account, making it suitable for use as a primary key // during account lookup. Email is not a good choice because it can be changed by the user. const userid = payload['sub']; // If the request specified a Google Workspace domain: // const domain = payload['hd']; } verify().catch(console.error);
verifyIdToken函数用于验证 JWT 签名、aud声明、exp声明 以及iss声明。如果您需要验证 ID 令牌是否代表 Google Workspace 或 Cloud 组织账号时,您可以查看
hd声明,该声明表示托管的 用户的网域。将资源访问权限限制为仅允许成员访问时,必须使用此设置 特定网域的用户缺少此声明即表示该账号不属于 Google 托管的域。</ph> 。 <ph type="x-smartling-placeholder">PHP 要在 PHP 中验证 ID 令牌,请使用适用于 PHP 的 Google API 客户端库。 安装该库(例如,使用 Composer):
然后,调用composer require google/apiclient
verifyIdToken()函数。例如:require_once 'vendor/autoload.php'; // Get $id_token via HTTPS POST. $client = new Google_Client(['client_id' => $WEB_CLIENT_ID]); // Specify the WEB_CLIENT_ID of the app that accesses the backend $payload = $client->verifyIdToken($id_token); if ($payload) { // This ID is unique to each Google Account, making it suitable for use as a primary key // during account lookup. Email is not a good choice because it can be changed by the user. $userid = $payload['sub']; // If the request specified a Google Workspace domain //$domain = $payload['hd']; } else { // Invalid ID token }
verifyIdToken函数用于验证 JWT 签名、aud声明、exp声明 以及iss声明。如果您需要验证 ID 令牌是否代表 Google Workspace 或 Cloud 组织账号时,您可以查看
hd声明,该声明表示托管的 用户的网域。将资源访问权限限制为仅允许成员访问时,必须使用此设置 特定网域的用户缺少此声明即表示该账号不属于 Google 托管的域。</ph> Python 要在 Python 中验证 ID 令牌,请使用 verify_oauth2_token 函数。例如:
from google.oauth2 import id_token from google.auth.transport import requests # (Receive token by HTTPS POST) # ... try: # Specify the WEB_CLIENT_ID of the app that accesses the backend: idinfo = id_token.verify_oauth2_token(token, requests.Request(), WEB_CLIENT_ID) # Or, if multiple clients access the backend server: # idinfo = id_token.verify_oauth2_token(token, requests.Request()) # if idinfo['aud'] not in [WEB_CLIENT_ID_1, WEB_CLIENT_ID_2, WEB_CLIENT_ID_3]: # raise ValueError('Could not verify audience.') # If the request specified a Google Workspace domain # if idinfo['hd'] != DOMAIN_NAME: # raise ValueError('Wrong domain name.') # ID token is valid. Get the user's Google Account ID from the decoded token. # This ID is unique to each Google Account, making it suitable for use as a primary key # during account lookup. Email is not a good choice because it can be changed by the user. userid = idinfo['sub'] except ValueError: # Invalid token pass
verify_oauth2_token函数验证 JWT 签名、aud声明和exp声明。 您还必须验证hd检查verify_oauth2_token返回。如果多个客户端访问 后端服务器,另请手动验证aud声明。- ID 令牌已由 Google 正确签名。使用 Google 的公钥(以 JWK 或 PEM 格式提供)验证令牌的签名。这些密钥会定期轮换;请检查响应中的
确认令牌有效后,您可以使用 Google ID 令牌中的信息来关联您网站的账号状态:
未注册的用户:您可以显示注册界面 (UI),让用户提供其他个人资料信息(如果需要)。它还允许用户以静默方式创建新账号和已登录的用户会话。
您的网站中已存在的现有账号:您可以显示一个网页,让最终用户输入密码,并将旧版账号与其 Google 凭据相关联。这确认了用户有权访问现有账号。
回访的联合用户:您可以静默登录用户。