透過「使用 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 帳戶登入」按鈕使用者介面

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

設定 專案

  1. 中開啟專案,或建立專案 (如果尚未建立)。
  2. 中,請確認所有資訊都完整且正確。
    1. 請確認應用程式已指派正確的應用程式名稱、應用程式標誌和應用程式首頁。這些值會在使用者註冊時,透過「使用 Google 帳戶登入」同意聲明畫面和「第三方應用程式和服務」畫面向使用者顯示。
    2. 請確認您已指定應用程式的隱私權政策和服務條款網址。
  3. 中,為應用程式建立 Android 用戶端 ID (如果尚未建立)。您必須指定應用程式的套件名稱和 SHA-1 簽名。
    1. 前往
    2. 按一下「Create client」
    3. 選取「Android」應用程式類型。
  4. 中,建立新的「Web application」用戶端 ID (如果尚未建立)。您可以先忽略「Authorized JavaScript Origins」和「Authorized redirect URIs」欄位。當後端伺服器與 Google 驗證服務通訊時,系統會使用這個用戶端 ID 來識別後端伺服器。
    1. 前往
    2. 按一下「Create client」
    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,讓供應商清除任何儲存的憑證工作階段。