如果您使用 Google 登入的應用程式或網站會與後端伺服器通訊,可能需要在伺服器上識別目前登入的使用者。為確保安全性,請在使用者順利登入後,使用 HTTPS 將使用者的 ID 權杖傳送至伺服器。接著,在伺服器上驗證完整性 並使用權杖內的使用者資訊建立 建立工作階段或建立新帳戶。
將 ID 權杖傳送至伺服器
首先,在使用者登入時取得 ID 權杖:
-
當您 設定 Google 登入, 呼叫
requestIdToken然後將 伺服器的網路用戶端 ID。// Request only the user's ID token, which can be used to identify the // user securely to your backend. This will contain the user's basic // profile (name, profile picture URL, etc) so you should not need to // make an additional call to personalize your application. GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(getString(R.string.server_client_id)) .requestEmail() .build();
-
應用程式啟動時,請檢查使用者是否已透過 Google 登入應用程式, 呼叫
silentSignIn來在這部裝置上或其他裝置:GoogleSignIn.silentSignIn() .addOnCompleteListener( this, new OnCompleteListener<GoogleSignInAccount>() { @Override public void onComplete(@NonNull Task<GoogleSignInAccount> task) { handleSignInResult(task); } });
-
如果使用者無法在背景登入,請呈現正常的登出體驗,讓使用者選擇登入。使用者執行動作時 簽名 in,請在活動中取得使用者的
GoogleSignInAccount登入意圖的結果:// This task is always completed immediately, there is no need to attach an // asynchronous listener. Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data); handleSignInResult(task);
-
在使用者以靜默或明確方式登入後,請從
GoogleSignInAccount物件取得 ID 權杖:private void handleSignInResult(@NonNull Task<GoogleSignInAccount> completedTask) { try { GoogleSignInAccount account = completedTask.getResult(ApiException.class); String idToken = account.getIdToken(); // TODO(developer): send ID Token to server and validate updateUI(account); } catch (ApiException e) { Log.w(TAG, "handleSignInResult:error", e); updateUI(null); } }
接著,請透過 HTTPS POST 要求將 ID 權杖傳送至伺服器:
HttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost("https://yourbackend.example.com/tokensignin"); try { List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1); nameValuePairs.add(new BasicNameValuePair("idToken", idToken)); httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs)); HttpResponse response = httpClient.execute(httpPost); int statusCode = response.getStatusLine().getStatusCode(); final String responseBody = EntityUtils.toString(response.getEntity()); Log.i(TAG, "Signed in as: " + responseBody); } catch (ClientProtocolException e) { Log.e(TAG, "Error sending ID token to backend.", e); } catch (IOException e) { Log.e(TAG, "Error sending ID token to backend.", e); }
驗證 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 帳戶。@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 中驗證 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 驗證 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 中驗證 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 中驗證 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 憑證附加資訊。
呼叫 tokeninfo 端點
驗證 ID 權杖簽名以進行偵錯的簡單方式,是
使用 tokeninfo 端點呼叫這個端點涉及
額外的網路要求,這類要求會對您進行大部分的驗證,
導入自己的 AI 程式碼驗證和酬載擷取機制。不適合用於正式環境
因為要求可能會受到節流限制
或以其他方式造成間歇性錯誤
如要使用 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 帳戶安全性重大異動 即可對你的服務採取行動來保護帳戶安全。