如果您使用 Google 登入的應用程式或網站會與後端伺服器通訊,可能需要在伺服器上識別目前登入的使用者。為了安全執行這項作業,請在使用者成功登入後,將 ID 權杖傳送至您的伺服器接著,在伺服器上驗證 ID 權杖的完整性,並使用權杖中包含的使用者資訊建立工作階段或建立新帳戶。
將 ID 權杖傳送至伺服器
使用者成功登入後,取得使用者的 ID 權杖:
Swift
GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in guard error == nil else { return } guard let signInResult = signInResult else { return } signInResult.user.refreshTokensIfNeeded { user, error in guard error == nil else { return } guard let user = user else { return } let idToken = user.idToken // Send ID token to backend (example below). } }
Objective-C
[GIDSignIn.sharedInstance signInWithPresentingViewController:self completion:^(GIDSignInResult * _Nullable signInResult, NSError * _Nullable error) { if (error) { return; } if (signInResult == nil) { return; } [signInResult.user refreshTokensIfNeededWithCompletion:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) { if (error) { return; } if (user == nil) { return; } NSString *idToken = user.idToken; // Send ID token to backend (example below). }]; }];
然後,使用 HTTPS POST 要求將 ID 權杖傳送至伺服器:
Swift
func tokenSignInExample(idToken: String) { guard let authData = try? JSONEncoder().encode(["idToken": idToken]) else { return } let url = URL(string: "https://yourbackend.example.com/tokensignin")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let task = URLSession.shared.uploadTask(with: request, from: authData) { data, response, error in // Handle response from your backend. } task.resume() }
Objective-C
NSString *signinEndpoint = @"https://yourbackend.example.com/tokensignin"; NSDictionary *params = @{@"idtoken": idToken}; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:signinEndpoint]; [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; [request setHTTPMethod:@"POST"]; [request setHTTPBody:[self httpBodyForParamsDictionary:params]]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { if (error) { NSLog(@"Error: %@", error.localizedDescription); } else { NSLog(@"Signed in as %@", data.bytes); } }];
驗證 ID 權杖的完整性
透過 HTTPS POST 接收 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 權杖的建議做法。
如要在 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 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 驗證 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(); 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 中驗證 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) { $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 中驗證 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. userid = idinfo['sub'] except ValueError: # Invalid token pass
verify_oauth2_token
函式會驗證 JWT
簽章、aud
聲明和 exp
聲明。
並驗證hd
來識別請求 (如果適用的話) 來檢查
會傳回 verify_oauth2_token
。如果多位用戶端存取
後端伺服器,也手動驗證 aud
憑證附加資訊。
调用 tokeninfo 端点
为调试验证 ID 令牌签名的一种简单方法是
使用 tokeninfo
端点。调用此端点涉及
这个额外的网络请求会为您完成大部分的验证工作,
验证和载荷提取。不适合在生产环境中使用
因为请求可能会受到限制或出现间歇性错误。
如需使用 tokeninfo
端点验证 ID 令牌,请创建 HTTPS
POST 或 GET 请求发送到端点,并在
id_token
参数。
例如,要验证令牌“XYZ123”,请发出以下 GET 请求:
https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123
如果令牌经过正确签名,并且 iss
和 exp
具有预期值,就会收到 HTTP 200 响应,其中正文
包含 JSON 格式的 ID 令牌声明。
以下是示例响应:
{ // 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" }
如果您需要验证 ID 令牌是否代表 Google Workspace 账号,可以先查看
hd
声明,指示用户的托管网域。只有在以下情况下,
从而仅允许特定网域中的成员访问资源。缺少此声明
表示该账号不属于 Google Workspace 托管网域。
建立帳戶或工作階段
驗證權杖後,請檢查使用者是否已在使用者資料庫中。如果是的話,請為使用者建立已驗證的工作階段。如果使用者尚未在使用者資料庫中,請根據 ID 權杖酬載中的資訊建立新的使用者記錄,並為使用者建立工作階段。在應用程式中偵測到新建立的使用者時,您可以提示使用者提供所需的其他個人資料。
使用跨帳戶保護功能保護使用者帳戶
只要您仰賴 Google 登入使用者,就能自動享有 安全功能和基礎架構,這是 Google 為了保護使用者資料而打造。不過 萬一使用者的 Google 帳戶遭到入侵,或發生其他 重大安全性事件,您的應用程式也較容易受到攻擊。為進一步保護帳戶免於遭受任何重大安全事件的影響,請使用跨帳戶保護功能,接收 Google 發出的安全警報。收到這些事件後,您就能掌握使用者 Google 帳戶安全性的重要變更,並採取行動保護帳戶。