使用 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 帳戶。@gmail.com- 為 true 且
hd已設定,則為 Google Workspace 帳戶。email_verified
使用者可以註冊 Google 帳戶,不必使用 Gmail 或 Google Workspace。如果
email不含@gmail.com後置字串,且沒有hd,Google 就不是授權機構,建議使用密碼或其他驗證方法來驗證使用者。email_verified也可能成立,因為 Google 最初是在建立 Google 帳戶時驗證使用者,但第三方電子郵件帳戶的擁有權可能已變更。強烈建議您使用適用於平台的 Google API 用戶端程式庫,或一般用途的 JWT 程式庫,而不要自行編寫程式碼來執行這些驗證步驟。如要進行開發和偵錯,可以呼叫我們的
tokeninfo驗證端點。使用 Google API 用戶端程式庫
使用其中一個 Google API 用戶端程式庫 (例如 Java、 Node.js、 PHP、 Python) 是在正式環境中驗證 Google ID 權杖的建議做法。
,瞭解如何調查及移除這項存取權。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憑證附加資訊不足以確保帳戶是由網域管理 或機構 ,瞭解如何調查及移除這項存取權。Node.js 如要透過 Node.js 驗證 ID 權杖,請使用 Node.js 適用的 Google 驗證程式庫。 安裝程式庫:
敬上 然後呼叫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 代管的網域 ,瞭解如何調查及移除這項存取權。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 代管的網域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 憑證。確認使用者可以存取現有帳戶。
回訪的同盟使用者:您可以讓使用者無須輸入憑證即可登入。