通过 Google 登录验证用户身份

通过 Google 登录可帮助您快速将用户身份验证集成到您的 Android 应用中。用户可以使用他们的 Google 帐号登录您的应用,提供同意,并安全地与您的应用共享其个人资料信息。Android 的凭据管理器 Jetpack 库使此集成变得流畅,使用单个 API 即可在 Android 设备上提供一致的体验。

本文档将指导您如何在 Android 应用中实现通过 Google 登录,如何设置通过 Google 登录按钮的用户界面,以及如何配置针对应用优化的一键注册和登录体验。为了实现平滑的设备迁移,通过 Google 登录支持自动登录,并且其跨平台特性(适用于 Android、iOS 和 Web 界面)可帮助您在任何设备上为您的应用提供登录访问权限。如果您为应用使用 Firebase 身份验证,您可以在其在 Android 上使用 Google 进行身份验证指南中了解有关集成通过 Google 登录和凭据管理器的更多信息。

要设置通过 Google 登录,请遵循以下两个主要步骤:

将“通过 Google 登录”配置为凭据管理器底部工作表 UI 的选项。这可以配置为自动提示用户登录。如果您已实现通行密钥或密码,您可以同时请求所有相关的凭据类型,这样用户就不必记住他们之前用于登录的选项。

Credential Manager bottom sheet
图 1. 凭据管理器底部工作表凭据选择 UI

将“通过 Google 登录”按钮添加到您的应用界面。“通过 Google 登录”按钮为用户提供了一种简化的方式,可使用其现有 Google 帐号注册或登录 Android 应用。如果用户关闭底部工作表 UI,或者他们明确希望使用其 Google 帐号进行注册和登录,他们将点击“通过 Google 登录”按钮。对于开发者而言,这意味着更轻松的用户引导和注册过程中的摩擦更小。

Animation showing the Sign in with Google flow
图 2. 凭据管理器“通过 Google 登录”按钮 UI

本文档解释了如何使用Google ID辅助库将“通过 Google 登录”按钮和底部工作表对话框与凭据管理器 API 集成。

设置您的 Google Cloud Console 项目

  1. Cloud Console中打开您的项目,或者如果尚未创建,则创建一个项目。
  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 文件中,使用最新版本的凭据管理器声明依赖项

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()

请记住正确处理退出登录,以便用户在明确退出您的应用后始终可以选择正确的帐号。

设置随机数以提高安全性

为了提高登录安全性并避免重放攻击,请添加setNonce以在每个请求中包含一个随机数。了解有关生成随机数的更多信息

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 成功后,提取CustomCredential,它保存了GoogleIdTokenCredential数据的结果。
  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()方法以清除所有凭据提供方中的当前用户凭据状态。这将通知所有凭据提供方,应清除给定应用的任何已存储凭据会话。

凭据提供方可能已存储活跃的凭据会话,并使用它来限制未来获取凭据调用的登录选项。例如,它可能会优先考虑活跃凭据而不是任何其他可用凭据。当您的用户明确退出您的应用时,为了在下次获得完整的登录选项,您应该调用此 API 以让提供方清除任何已存储的凭据会话。