使用“使用 Google 账号登录”功能对用户进行身份验证

借助使用 Google 账号登录功能,您可以快速将用户身份验证功能集成到 Android 应用中。用户可以使用自己的 Google 账号登录您的应用、提供同意声明,并安全地与您的应用分享自己的个人资料信息。Android 的 Credential Manager Jetpack 库可让这种集成顺畅无阻,通过单个 API 在各种 Android 设备上提供一致的体验。

本文档将引导您在 Android 应用中实现“使用 Google 账号登录”功能,介绍如何设置“使用 Google 账号登录”按钮界面,以及如何配置针对应用优化的注册和登录一键快捷功能。为了实现顺畅的设备迁移,Google 登录支持自动登录,并且其跨平台特性(支持 Android、iOS 和 Web 平台)有助于您在任何设备上为应用提供登录访问权限。如果您为应用使用 Firebase Authentication,可以参阅在 Android 上通过 Google 进行身份验证指南,详细了解如何集成“通过 Google 登录”和 Credential Manager。

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

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

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

将“使用 Google 账号登录”按钮添加到应用的界面中。借助“使用 Google 账号登录”按钮,用户可以轻松地使用其现有的 Google 账号注册或登录 Android 应用。如果用户关闭底部操作表界面,或者明确想要使用其 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. 点击创建客户端
    3. 选择 Android 应用类型。
  4. 中,创建一个新的“Web 应用”客户端 ID(如果您还没有)。您可以暂时忽略“已获授权的 JavaScript 来源”和“已获授权的重定向 URI”字段。此客户端 ID 将用于在您的后端服务器与 Google 的身份验证服务通信时标识该服务器。
    1. 前往
    2. 点击创建客户端
    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)
  // nonce string to use when generating a Google ID token
  .setNonce(nonce)
.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)
  // nonce string to use when generating a Google ID token
  .setNonce(nonce)
.build()

在实现自动登录功能时,请务必正确处理退出登录,以便用户在明确退出您的应用后,始终可以选择合适的账号。

设置随机数以提高安全性

为了提高登录安全性并避免重放攻击,请添加 setNonce 在每个请求中添加 Nonce。详细了解如何生成 Nonce

val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
  .setFilterByAuthorizedAccounts(true)
  .setServerClientId(WEB_CLIENT_ID)
  .setAutoSelectEnabled(true)
  // nonce string to use when generating a Google ID token
  .setNonce(nonce)
.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 = GetCredentialRequest.Builder()
  .addCredentialOption(googleIdOption)
  .build()

coroutineScope {
  try {
    val result = credentialManager.getCredential(
      request = request,
      context = activityContext,
    )
    handleSignIn(result)
  } catch (e: GetCredentialException) {
    // Handle failure
  }
}
fun handleSignIn(result: GetCredentialResponse) {
  // Handle the successfully returned credential.
  val credential = result.credential
  val responseJson: String

  when (credential) {

    // Passkey credential
    is PublicKeyCredential -> {
      // Share responseJson such as a GetCredentialResponse to 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.
          // see [validation instructions](https://developers.google.com/identity/gsi/web/guides/verify-google-id-token)
        } 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(
  serverClientId = WEB_CLIENT_ID
).setNonce(nonce)
  .build()

按照以下代码示例中的说明处理返回的 GoogleIdTokenCredential

fun handleSignInWithGoogleOption(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,以便提供方清除任何已存储的凭据会话。