使用 Google 登录进行用户身份验证

Google 登录 可帮助您快速将用户身份验证与您的 Android 应用集成。用户可以使用其 Google 帐户登录您的应用,提供同意,并安全地与您的应用共享其个人资料信息。Android 的 Credential Manager Jetpack 库使这种集成变得流畅,通过单个 API 在 Android 设备上提供一致的体验。

本文档指导您在 Android 应用中实施 Google 登录,如何设置 Google 登录按钮 UI,以及配置应用优化的单点登录和注册体验。为了实现流畅的设备迁移,Google 登录支持自动登录,并且其跨 Android、iOS 和 Web 界面的跨平台特性可帮助您在任何设备上为您的应用提供登录访问权限。

要设置 Google 登录,请按照以下两个主要步骤操作

将 Google 登录配置为 Credential Manager 底部工作表 UI 的选项。可以将其配置为自动提示用户登录。如果您已实施 密钥或密码,您可以同时请求所有相关凭据类型,以便用户不必记住以前用来登录的选项。

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

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

Animation showing the Sign in with Google flow
图 2. Credential Manager Google 登录按钮 UI

本文档说明了如何使用 Google ID 帮助程序库将 Google 登录按钮和底部工作表对话框与 Credential Manager API 集成。

设置您的 Google API 控制台项目

  1. API 控制台 中打开您的项目,或者如果您还没有项目,则创建一个项目。
  2. 在 OAuth 同意屏幕页面上,确保所有信息完整且准确。
    1. 确保您的应用已分配正确的应用名称、应用徽标和应用主页。这些值将在注册时的 Google 登录同意屏幕和 第三方应用和服务屏幕 上显示给用户。
    2. 确保您已指定应用的隐私政策和服务条款的 URL。
  3. 在“凭据”页面上,为您的应用创建 Android 客户端 ID(如果您还没有)。您需要指定应用的包名称和 SHA-1 签名。
    1. 转到 凭据页面
    2. 点击创建凭据 > OAuth 客户端 ID
    3. 选择Android应用类型。
  4. 在“凭据”页面上,创建一个新的“Web 应用”客户端 ID(如果您还没有)。现在可以忽略“授权的 JavaScript 来源”和“授权的重定向 URI”字段。此客户端 ID 用于在您的后端服务器与 Google 的身份验证服务通信时识别您的后端服务器。
    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()

首先,通过使用设置为 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 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 以让提供程序清除任何存储的凭据会话。