透過「使用 Google 帳戶登入」功能驗證使用者

使用 Google 帳戶登入可協助您快速將使用者驗證功能整合至 Android 應用程式。使用者可以使用 Google 帳戶登入您的應用程式、提供同意聲明,並安全地將個人資料與您的應用程式分享。Android 的 Credential Manager Jetpack 程式庫可讓這項整合作業順利進行,並透過單一 API 在各 Android 裝置上提供一致的體驗。

本文將引導您在 Android 應用程式中實作「使用 Google 帳戶登入」功能,說明如何設定「使用 Google 帳戶登入」按鈕 UI,以及如何設定應用程式最佳化 One Tap 註冊和登入體驗。為了順利進行裝置遷移,使用 Google 帳戶登入功能可支援自動登入,而且跨平台的 Android、iOS 和網頁介面特性,可協助您在任何裝置上提供應用程式的登入存取權。

如要設定「使用 Google 帳戶登入」功能,請按照下列兩個主要步驟操作:

將「使用 Google 帳戶登入」設定為 Credential Manager 底部功能表 UI 的選項。您可以設定這項功能,讓系統自動提示使用者登入。如果您已導入 密碼金鑰或密碼,可以同時要求所有相關的憑證類型,這樣使用者就不必記得先前用來登入的選項。

Credential Manager 底部功能表
圖 1. Credential Manager 底部功能表的憑證選取 UI

在應用程式的 UI 中加入「使用 Google 帳戶登入」按鈕。「使用 Google 帳戶登入」按鈕可讓使用者以簡化的方式,使用現有的 Google 帳戶註冊或登入 Android 應用程式。如果使用者關閉底部功能表 UI,或是明確想要使用 Google 帳戶登入/註冊,就會點選「使用 Google 帳戶登入」按鈕。對開發人員而言,這代表使用者更容易完成新手上路流程,註冊過程也更順暢。

動畫:顯示「使用 Google 帳戶登入」流程
圖 2. Credential Manager 的「使用 Google 帳戶登入」按鈕 UI

本文說明如何使用 Google ID 輔助程式庫,將「使用 Google 帳戶登入」按鈕和底部功能表對話方塊與 Credential Manager API 整合。

設定 Google API 控制台專案

  1. API 控制台中開啟專案,或建立專案 (如果尚未建立)。
  2. 在 OAuth 同意畫面頁面中,確認所有資訊完整且正確無誤。
    1. 請確認應用程式已指派正確的應用程式名稱、應用程式標誌和應用程式首頁。這些值會在使用者註冊時,透過「使用 Google 帳戶登入」同意聲明畫面和「第三方應用程式和服務」畫面向使用者顯示。
    2. 請確認您已指定應用程式的隱私權政策和服務條款網址。
  3. 在「憑證」頁面中,為應用程式建立 Android 用戶端 ID (如果尚未建立)。您必須指定應用程式的套件名稱和 SHA-1 簽名。
    1. 前往「憑證」頁面
    2. 依序點選「建立憑證」>「OAuth 用戶端 ID」
    3. 選取「Android」應用程式類型。
  4. 在「憑證」頁面中,建立新的「網頁應用程式」用戶端 ID (如果尚未建立)。您可以先忽略「Authorized JavaScript Origins」和「Authorized redirect URIs」欄位。當後端伺服器與 Google 驗證服務通訊時,這個用戶端 ID 會用於識別後端伺服器。
    1. 前往「憑證」頁面
    2. 依序點選「建立憑證」>「OAuth 用戶端 ID」
    3. 選取網路應用程式類型。

宣告依附元件

在模組的 build.gradle 檔案中,使用最新版本的 Credential Manager 宣告依附元件:

dependencies {
  // ... other dependencies

  implementation "androidx.credentials:credentials:<latest version>"
  implementation "androidx.credentials:credentials-play-services-auth:<latest version>"
  implementation "com.google.android.libraries.identity.googleid:googleid:<latest version>"
}

將 Google 登入要求例項化

如要開始實作,請將 Google 登入要求例項化。請使用 GetGoogleIdOption 擷取使用者的 Google ID 權杖。

val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
  .setFilterByAuthorizedAccounts(true)
  .setServerClientId(WEB_CLIENT_ID)
  .setAutoSelectEnabled(true)
  .setNonce(<nonce string to use when generating a Google ID token>)
  .build()

首先,請呼叫 API,並將 setFilterByAuthorizedAccounts 參數設為 true,檢查使用者是否有任何帳戶曾用於登入您的應用程式。使用者可以選擇要登入哪個帳戶。

如果沒有可授權的 Google 帳戶,系統應會提示使用者使用任何可用的帳戶登入。如要這麼做,請再次呼叫 API 並將 setFilterByAuthorizedAccounts 設為 false,提示使用者。進一步瞭解如何註冊

為回訪使用者啟用自動登入功能 (建議)

開發人員應為使用單一帳戶註冊的使用者啟用自動登入功能。這麼做可在多部裝置間提供流暢的體驗,特別是在裝置遷移期間,使用者不必重新輸入憑證,就能快速重新存取帳戶。對於使用者而言,這可在他們先前已登入時,消除不必要的摩擦。

如要啟用自動登入功能,請使用 setAutoSelectEnabled(true)。只有在符合下列條件時,才能自動登入:

  • 只有一個憑證符合要求,可能是 Google 帳戶或密碼,且這項憑證與 Android 裝置上的預設帳戶相符。
  • 使用者未明確登出。
  • 使用者未在 Google 帳戶設定中停用自動登入功能。
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
  .setFilterByAuthorizedAccounts(true)
  .setServerClientId(WEB_CLIENT_ID)
  .setAutoSelectEnabled(true)
  .setNonce(<nonce string to use when generating a Google ID token>)
  .build()

實作自動登入功能時,請務必正確處理登出程序,以便使用者在明確登出應用程式後,隨時都能選擇正確的帳戶。

設定 Nonce 來提升安全性

為提升登入安全性並避免重送攻擊,請新增 setNonce,在每項要求中加入 Nonce。進一步瞭解如何產生 Nonce

val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
  .setFilterByAuthorizedAccounts(true)
  .setServerClientId(WEB_CLIENT_ID)
  .setAutoSelectEnabled(true)
  .setNonce(<nonce string to use when generating a Google ID token>)
  .build()

建立「使用 Google 帳戶登入」流程

設定「使用 Google 帳戶登入」流程的步驟如下:

  1. GetCredentialRequest 例項化,然後使用 addCredentialOption() 新增先前建立的 googleIdOption,以便擷取憑證。
  2. 將這項要求傳遞至 getCredential() (Kotlin) 或 getCredentialAsync() (Java) 呼叫,擷取使用者可用的憑證。
  3. API 成功後,請擷取保留 GoogleIdTokenCredential 資料結果的 CustomCredential
  4. CustomCredential 的類型應等於 GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL 的值。請使用 GoogleIdTokenCredential.createFrom 方法,將物件轉換為 GoogleIdTokenCredential
  5. 如果轉換成功,請擷取並驗證 GoogleIdTokenCredential ID,然後在伺服器上驗證憑證。

  6. 如果轉換作業因 GoogleIdTokenParsingException 失敗,您可能需要更新使用 Google 帳戶登入資料庫的版本。

  7. 找出所有無法辨識的自訂憑證類型。

val request: GetCredentialRequest = Builder()
  .addCredentialOption(googleIdOption)
  .build()

coroutineScope.launch {
  try {
    val result = credentialManager.getCredential(
      request = request,
      context = activityContext,
    )
    handleSignIn(result)
  } catch (e: GetCredentialException) {
    handleFailure(e)
  }
}

fun handleSignIn(result: GetCredentialResponse) {
  // Handle the successfully returned credential.
  val credential = result.credential

  when (credential) {

    // Passkey credential
    is PublicKeyCredential -> {
      // Share responseJson such as a GetCredentialResponse on your server to
      // validate and authenticate
      responseJson = credential.authenticationResponseJson
    }

    // Password credential
    is PasswordCredential -> {
      // Send ID and password to your server to validate and authenticate.
      val username = credential.id
      val password = credential.password
    }

    // GoogleIdToken credential
    is CustomCredential -> {
      if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
        try {
          // Use googleIdTokenCredential and extract the ID to validate and
          // authenticate on your server.
          val googleIdTokenCredential = GoogleIdTokenCredential
            .createFrom(credential.data)
          // You can use the members of googleIdTokenCredential directly for UX
          // purposes, but don't use them to store or control access to user
          // data. For that you first need to validate the token:
          // pass googleIdTokenCredential.getIdToken() to the backend server.
          GoogleIdTokenVerifier verifier = ... // see validation instructions
          GoogleIdToken idToken = verifier.verify(idTokenString);
          // To get a stable account identifier (e.g. for storing user data),
          // use the subject ID:
          idToken.getPayload().getSubject()
        } catch (e: GoogleIdTokenParsingException) {
          Log.e(TAG, "Received an invalid google id token response", e)
        }
      } else {
        // Catch any unrecognized custom credential type here.
        Log.e(TAG, "Unexpected type of credential")
      }
    }

    else -> {
      // Catch any unrecognized credential type here.
      Log.e(TAG, "Unexpected type of credential")
    }
  }
}

觸發「使用 Google 帳戶登入」按鈕流程

如要觸發「使用 Google 帳戶登入」按鈕流程,請使用 GetSignInWithGoogleOption (而非 GetGoogleIdOption):

val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder()
  .setServerClientId(WEB_CLIENT_ID)
  .setNonce(<nonce string to use when generating a Google ID token>)
  .build()

請按照以下程式碼範例所述,處理傳回的 GoogleIdTokenCredential

fun handleSignIn(result: GetCredentialResponse) {
  // Handle the successfully returned credential.
  val credential = result.credential

  when (credential) {
    is CustomCredential -> {
      if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
        try {
          // Use googleIdTokenCredential and extract id to validate and
          // authenticate on your server.
          val googleIdTokenCredential = GoogleIdTokenCredential
            .createFrom(credential.data)
        } catch (e: GoogleIdTokenParsingException) {
          Log.e(TAG, "Received an invalid google id token response", e)
        }
      }
      else -> {
        // Catch any unrecognized credential type here.
        Log.e(TAG, "Unexpected type of credential")
      }
    }

    else -> {
      // Catch any unrecognized credential type here.
      Log.e(TAG, "Unexpected type of credential")
    }
  }
}

將 Google 登入要求例項化後,請採用「使用 Google 帳戶登入」一節所述的類似方式啟動驗證流程。

啟用新使用者的註冊功能 (建議採用)

使用者只要輕觸幾下,就能透過「使用 Google 帳戶登入」功能,在您的應用程式或服務中建立新帳戶。

如果找不到已儲存的憑證 (getGoogleIdOption 不會傳回任何 Google 帳戶),請提示使用者登入。首先,請檢查 setFilterByAuthorizedAccounts(true),看看是否有任何先前使用的帳戶。如果找不到任何帳戶,請使用 setFilterByAuthorizedAccounts(false) 提示使用者使用 Google 帳戶註冊

例子:

val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
  .setFilterByAuthorizedAccounts(false)
  .setServerClientId(WEB_CLIENT_ID)
  .build()

將 Google 註冊要求例項化後,請啟動驗證流程。如果使用者不想使用「使用 Google 帳戶登入」功能註冊,建議您為應用程式進行自動填入功能最佳化。使用者建立帳戶後,建議您將他們加入密碼金鑰,做為建立帳戶的最後步驟。

處理登出

當使用者登出應用程式時,請呼叫 API clearCredentialState() 方法,清除所有憑證提供者的目前使用者憑證狀態。這會通知所有憑證提供者,應清除特定應用程式的任何已儲存憑證工作階段。

憑證提供者可能已儲存有效的憑證工作階段,並用於限制日後的 get-credential 呼叫登入選項。舉例來說,系統可能會將有效憑證優先於其他可用的憑證。當使用者明確登出應用程式,並希望在下次登入時取得完整的登入選項時,您應呼叫此 API,讓供應商清除任何儲存的憑證工作階段。