透過後端伺服器驗證

如果您使用 Google 登入的應用程式或網站會與後端伺服器通訊,您可能需要在伺服器上識別目前登入的使用者。為安全地完成這項程序,請在使用者成功登入後,透過 HTTPS 將使用者的 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 的公钥(以 JWKPEM 格式提供)来验证令牌的签名。这些密钥会定期轮替;请检查响应中的 Cache-Control 标头,以确定何时应再次检索它们。
  • ID 令牌中 aud 的值等于应用的某个客户端 ID。通过进行这项检查,可以防止向恶意应用颁发的 ID 令牌用于访问应用后端服务器上同一用户的数据。
  • ID 令牌中 iss 的值等于 accounts.google.comhttps://accounts.google.com
  • ID 令牌的过期时间 (exp) 尚未过去。
  • 如果您需要验证该 ID 令牌代表的是 Google Workspace 或 Cloud 组织帐号,则可以检查 hd 声明,该声明指示用户的托管网域。仅允许特定网域的成员访问资源时,必须使用此方法。缺少此声明表示帐号不属于 Google 托管的网域。

我们强烈建议您使用适合您的平台的 Google API 客户端库或通用 JWT 库,而不是自行编写代码来执行这些验证步骤。对于开发和调试,您可以调用我们的 tokeninfo 验证端点。

使用 Google API 用戶端程式庫

建議您使用其中一種 Google API 用戶端程式庫 (例如 JavaNode.jsPHPPython),在實際工作環境中驗證 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 CLIENT_ID of the app that accesses the backend:
    .setAudience(Collections.singletonList(CLIENT_ID))
    // Or, if multiple clients access the backend:
    //.setAudience(Arrays.asList(CLIENT_ID_1, CLIENT_ID_2, 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 機構帳戶,可以檢查 Payload.getHostedDomain() 方法傳回的網域名稱,藉此驗證 hd 憑證附加資訊。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: CLIENT_ID,  // Specify the CLIENT_ID of the app that accesses the backend
      // Or, if multiple clients access the backend:
      //[CLIENT_ID_1, CLIENT_ID_2, 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

如要以 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' => $CLIENT_ID]);  // Specify the 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

如要在 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 CLIENT_ID of the app that accesses the backend:
    idinfo = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID)

    # Or, if multiple clients access the backend server:
    # idinfo = id_token.verify_oauth2_token(token, requests.Request())
    # if idinfo['aud'] not in [CLIENT_ID_1, CLIENT_ID_2, 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 憑證附加資訊。您也必須檢查 verify_oauth2_token 傳回的物件,驗證 hd 憑證附加資訊 (如適用)。如有多個用戶端存取後端伺服器,也請手動驗證 aud 憑證附加資訊。

调用 tokeninfo 端点

若要验证用于调试的 ID 令牌签名,一种简单的方法是使用 tokeninfo 端点。调用此端点涉及到一个额外的网络请求,该网络请求会为您执行大部分验证,而您在自己的代码中测试适当的验证和载荷提取时。它不适合在生产代码中使用,因为请求可能会受到限制或出现间歇性错误。

如需使用 tokeninfo 端点验证 ID 令牌,请向该端点发出 HTTPS POST 或 GET 请求,并在 id_token 参数中传递您的 ID 令牌。例如,要验证令牌“XYZ123”,可发出以下 GET 请求:

https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123

如果令牌已正确签名,并且 issexp 声明具有预期值,您会收到 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 帳戶安全有重大異動,並能對服務採取動作,保護帳戶安全。