將憑證管理工具與「使用 Google 帳戶登入」功能整合

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

本文件會逐步引導您實作 Android 中的「使用 Google 帳戶登入」功能、如何設定「使用 Google 帳戶登入」按鈕 UI,以及設定專為應用程式設計的一鍵註冊和登入體驗。為順利遷移裝置,「使用 Google 帳戶登入」功能支援自動登入功能,「使用 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。

設定 Google API 控制台專案

  1. API 控制台開啟專案;如果您還沒有專案,請先建立專案。
  2. 在 OAuth 同意畫面上,確認所有資訊皆完整正確。
    1. 確認您的應用程式具有正確的應用程式名稱、應用程式標誌和應用程式首頁。這些值會在使用者註冊時,於「使用 Google 帳戶登入」同意畫面顯示,以及第三方應用程式和服務畫面
    2. 請確認您已指定應用程式隱私權政策和服務條款的網址。
  3. 如果您尚未在「憑證」頁面中,為應用程式建立 Android 用戶端 ID,請先建立。您需要指定應用程式的套件名稱和 SHA-1 簽名。
    1. 前往「Credentials」(憑證) 頁面
    2. 按一下「Create credentials」(建立憑證) >「OAuth client ID」(OAuth 用戶端 ID)
    3. 選取「Android」應用程式類型。
  4. 如果您尚未在「憑證」頁面中建立新的「網頁應用程式」用戶端 ID,請先建立一個。您可以暫時忽略「已授權的 JavaScript 來源」和「已授權的重新導向 URI」欄位。這個用戶端 ID 會用來識別與 Google 驗證服務通訊的後端伺服器。
    1. 前往「Credentials」(憑證) 頁面
    2. 按一下「Create credentials」(建立憑證) >「OAuth client ID」(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()

首先,使用設為 truesetFilterByAuthorizedAccounts 參數呼叫 API,檢查使用者是否有先前曾用於登入應用程式的帳戶。使用者可以選擇登入的可用帳戶。

如果沒有可用的已授權的 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 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 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(SERVER_CLIENT_ID)
  .build()

將 Google 註冊要求例項化後,請啟動驗證流程。如果使用者不想使用 Google 註冊,請考慮使用自動填入服務或密碼金鑰建立帳戶。

處理登出動作

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

憑證提供者可能會儲存執行中的憑證工作階段,並用於限制日後取得憑證呼叫的登入選項。例如,其優先順序可能會優先使用有效的憑證,而非其他任何可用的憑證。當使用者明確登出應用程式,並在下次取得完整的登入選項時,您應呼叫這個 API,讓提供者清除任何已儲存的憑證工作階段。