使用 Google Identity Services 或 OAuth 2.0 授權碼流程時,Google 會透過 POST 方法將 ID 權杖傳回重新導向端點。或者,OIDC 隱含流程會使用 GET 要求。因此,應用程式有責任將收到的憑證安全地傳輸至伺服器。
這是隱含流程,ID 權杖會傳回至網址片段中,用戶端 JavaScript 必須剖析該片段。應用程式有責任實作自己的驗證機制,確保要求真實性並防範 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 程式庫會在 POST 要求主體中加入這個權杖做為參數,同樣命名為
g_csrf_token。 - 比較兩個權杖值
- 如果這兩個值都存在且完全相符,系統就會將要求視為合法,且來自您的網域。
- 如果值不存在或不相符,伺服器必須拒絕要求。這項檢查可確保要求是從您網域上執行的 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 憑證。確認使用者可以存取現有帳戶。
回訪的同盟使用者:您可以讓使用者無須輸入憑證即可登入。