将凭据管理器与“使用 Google 账号登录”集成

使用 Google 帐号登录可帮助您快速将用户身份验证与 Android 应用集成。用户可以使用其 Google 帐号登录您的应用、提供同意声明以及安全地与您的应用共享其个人资料信息。Android 的 Credential Manager Jetpack 库让这一集成能够顺畅运行,只需使用单个 API 即可在各种 Android 设备上提供一致的体验。

本文档将指导您在 Android 应用中实现“使用 Google 账号登录”功能、如何设置“使用 Google 账号登录”按钮界面,以及如何配置针对应用进行了优化的一键注册和登录体验。为了顺利完成设备迁移,“使用 Google 帐号登录”功能支持自动登录,该机制跨 Android、iOS 和 Web 平台具有跨平台的特性,可帮助您在任何设备上提供应用的登录权限。

如需设置“使用 Google 帐号登录”功能,请按照以下两个主要步骤操作:

将“使用 Google 帐号登录”配置为 Credential Manager 底部动作条界面的选项。此设置可以配置为自动提示用户登录。 如果您实现了通行密钥或密码,可以同时请求所有相关的凭据类型,这样用户就不必记住他们之前用于登录的选项。

凭据管理器底部动作条
图 1. Credential Manager 底部动作条凭据选择界面

向应用的界面添加“使用 Google 账号登录”按钮。通过“使用 Google 账号登录”按钮,用户可以轻松使用现有 Google 账号注册或登录 Android 应用。如果用户关闭底部动作条界面,或者他们明确想要使用其 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. 前往“凭据”页面
    2. 依次点击创建凭据 > OAuth 客户端 ID
    3. 选择 Android 应用类型。
  4. 在“凭据”页面中,创建新的“Web 应用”客户端 ID(如果尚未创建)。您可以暂时忽略“已获授权的 JavaScript 来源”和“已获授权的重定向 URI”字段。在与 Google 的身份验证服务通信时,此客户端 ID 将用于标识您的后端服务器。
    1. 前往“凭据”页面
    2. 依次点击创建凭据 > OAuth 客户端 ID
    3. 选择 Web 应用类型。

声明依赖项

在模块的 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 并添加之前创建的 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()
  .addGetCredentialOption(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() 方法,以从所有凭据提供程序中清除当前用户凭据状态。这将通知所有凭据提供程序,应清除为给定应用存储的任何凭据会话。

凭据提供程序可能存储了处于活动状态的凭据会话,并使用该会话为将来的 get-credential 调用限制登录选项。例如,它可以优先处理有效凭据,而非任何其他可用凭据。当用户明确退出您的应用,以便下次获得全面的登录选项时,您应调用此 API,让提供程序清除所有存储的凭据会话。