让用户使用已保存的凭据登录

使用一键式登录客户端向用户请求权限以检索 之前用来登录您的应用的凭据之一。这些 凭据可以是 Google 账号,也可以是用户名-密码组合 您通过 Chrome、Android 自动填充功能或 Smart Lock 保存给 Google 的 密码。

一键登录界面

成功检索到凭据后,您就可以使用 使用户登录您的应用

如果用户未保存任何凭据,系统不会显示任何界面,您可以 提供正常的退出账号体验。

应该在哪里使用一键登录?

如果您的应用要求用户登录,请在登录时显示一键快捷界面 屏幕。即使您已经启用了“使用 Google 账号登录”功能,此操作也很有帮助 按钮:由于一键式界面可以配置为仅显示凭据, 则可以设置提醒,以提醒不常登录的用户 并防止他们意外登录 创建新账号

如果登录对于您的应用而言是可选的,请考虑在任何设备上均可使用一键登录 屏幕,让用户通过登录即可获享更出色的体验。例如,如果用户可以 在未登录状态下使用您的应用浏览内容,但只能发表评论或添加 将商品添加到了购物车 - 这对于消费者来说 一键登录。

可选登录应用也应在登录屏幕上使用一键登录功能, 出于上述原因。

准备工作

  • 按照说明设置您的 Google API 控制台项目和 Android 项目 开始使用一键登录
  • 如果您支持基于密码的登录,请针对 自动填充(或使用 Smart Lock(密码专用)),这样用户就可以保存自己的 然后再登录即可

1. 配置一键登录客户端

您可以配置一键登录客户端,让用户使用已保存的 密码、已保存的 Google 账号,或两者之一。(建议同时支持这两种工具, 为新用户启用一键创建账号以及自动登录或一键登录功能 。)

如果您的应用使用基于密码的登录方式,请使用 setPasswordRequestOptions() 执行以下操作: 启用密码凭据请求。

如果您的应用使用 Google 登录功能,请使用 setGoogleIdTokenRequestOptions() 执行以下操作: 启用并配置 Google ID 令牌请求:

  • 将服务器客户端 ID 设置为您在 Google API 中创建的 ID 控制台。请注意,这是您的服务器的客户端 ID,而不是 您的 Android 客户端 ID

  • 将客户端配置为按已获授权的账号进行过滤。启用此选项后, 选项时,一键式客户端只会提示用户 过去已用过的 Google 账号。这样做有助于用户登录 因为他们不确定自己是否已经拥有账号或 以及防止用户意外创建新的 Google 账号, 与您的应用关联。

  • 如果您希望尽可能让用户自动登录,请启用此功能 尽在 setAutoSelectEnabled()。只有在以下情况下,系统才支持自动登录: 满足以下条件:

    • 用户只有一个为您的应用保存的凭据。也就是说,一个已存的密码或一个已存的 Google 账号。
    • 用户尚未在其 Google 账号设置中停用自动登录功能。
  • 虽然是可选操作,但我们强烈建议您考虑使用 Nonce 来 提高登录安全性并避免重放攻击。使用 setNonce 在每个请求中添加 Nonce。请参阅 SafetyNet 的 获取 Nonce 部分,了解有关生成 Nonce 的建议和更多详情。

Java

public class YourActivity extends AppCompatActivity {
  // ...

  private SignInClient oneTapClient;
  private BeginSignInRequest signInRequest;

  @Override
  public void onCreate(@Nullable Bundle savedInstanceState,
                       @Nullable PersistableBundle persistentState) {
      super.onCreate(savedInstanceState, persistentState);

      oneTapClient = Identity.getSignInClient(this);
      signInRequest = BeginSignInRequest.builder()
              .setPasswordRequestOptions(PasswordRequestOptions.builder()
                      .setSupported(true)
                      .build())
              .setGoogleIdTokenRequestOptions(GoogleIdTokenRequestOptions.builder()
                      .setSupported(true)
                      // Your server's client ID, not your Android client ID.
                      .setServerClientId(getString(R.string.default_web_client_id))
                      // Only show accounts previously used to sign in.
                      .setFilterByAuthorizedAccounts(true)
                      .build())
              // Automatically sign in when exactly one credential is retrieved.
              .setAutoSelectEnabled(true)
              .build();
      // ...
  }
  // ...
}

Kotlin

class YourActivity : AppCompatActivity() {
    // ...

    private lateinit var oneTapClient: SignInClient
    private lateinit var signInRequest: BeginSignInRequest

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        oneTapClient = Identity.getSignInClient(this)
        signInRequest = BeginSignInRequest.builder()
            .setPasswordRequestOptions(BeginSignInRequest.PasswordRequestOptions.builder()
                .setSupported(true)
                .build())
            .setGoogleIdTokenRequestOptions(
                BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
                    .setSupported(true)
                    // Your server's client ID, not your Android client ID.
                    .setServerClientId(getString(R.string.your_web_client_id))
                    // Only show accounts previously used to sign in.
                    .setFilterByAuthorizedAccounts(true)
                    .build())
            // Automatically sign in when exactly one credential is retrieved.
            .setAutoSelectEnabled(true)
            .build()
        // ...
    }
    // ...
}

2. 检查用户是否已登录

如果已登录的用户或未登录的用户都可以使用您的 Activity,请查看 用户状态,然后再显示一键登录界面。

您还应跟踪用户是否已经拒绝使用 通过关闭提示或点按提示外部的屏幕,实现一键登录。这可以 像 Activity 的布尔属性一样简单。 (请参阅下文的停止显示一键式界面。)

3. 显示一键登录界面

如果用户没有登录,也没有拒绝使用一键登录, 调用客户端对象的 beginSignIn() 方法,并将监听器附加到 Task。应用通常会在 activity 的 onCreate() 方法中执行此操作 或屏幕转换之后(使用单 activity 架构时)。

如果用户已保存任何事件,一键式客户端将调用成功监听器 提供凭据在成功监听器中,从 Task 结果并将其传递给 startIntentSenderForResult() 以开始 一键登录界面。

如果用户没有任何已保存的凭据,一键式客户端会调用 失败监听器。在这种情况下,您无需采取任何措施:只需继续 呈现应用的退出体验。但是,如果您支持一键快捷功能 则可以从此处开始此流程 体验请参阅一键创建新账号

Java

oneTapClient.beginSignIn(signUpRequest)
        .addOnSuccessListener(this, new OnSuccessListener<BeginSignInResult>() {
            @Override
            public void onSuccess(BeginSignInResult result) {
                try {
                    startIntentSenderForResult(
                            result.getPendingIntent().getIntentSender(), REQ_ONE_TAP,
                            null, 0, 0, 0);
                } catch (IntentSender.SendIntentException e) {
                    Log.e(TAG, "Couldn't start One Tap UI: " + e.getLocalizedMessage());
                }
            }
        })
        .addOnFailureListener(this, new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // No saved credentials found. Launch the One Tap sign-up flow, or
                // do nothing and continue presenting the signed-out UI.
                Log.d(TAG, e.getLocalizedMessage());
            }
        });

Kotlin

oneTapClient.beginSignIn(signInRequest)
    .addOnSuccessListener(this) { result ->
        try {
            startIntentSenderForResult(
                result.pendingIntent.intentSender, REQ_ONE_TAP,
                null, 0, 0, 0, null)
        } catch (e: IntentSender.SendIntentException) {
            Log.e(TAG, "Couldn't start One Tap UI: ${e.localizedMessage}")
        }
    }
    .addOnFailureListener(this) { e ->
        // No saved credentials found. Launch the One Tap sign-up flow, or
        // do nothing and continue presenting the signed-out UI.
        Log.d(TAG, e.localizedMessage)
    }

4. 处理用户的响应

系统会将用户对一键登录提示的响应报告给您的应用 使用 Activity 的 onActivityResult() 方法。如果用户选择登录 结果将是一个已保存的凭据。如果用户拒绝登录,请执行以下任一操作: 关闭一键快捷界面或点按界面以外的地方,就会返回结果 代码 RESULT_CANCELED。您的应用需要处理这两种可能性。

使用检索到的凭据登录

如果用户选择与您的应用共享凭据,您可以通过以下方式检索凭据: 将 intent 数据从 onActivityResult() 传递到一键式客户端的 getSignInCredentialFromIntent() 方法。凭据将具有非空 googleIdToken 属性(如果用户与之共享了 Google 账号凭据) 或者一个非 null password 属性(如果用户分享了已保存的密码)。

使用该凭据进行应用后端身份验证。

  • 如果系统检索到了用户名和密码对,请将它们用于登录相同的账号 与用户手动提供这些值的方法相同。
  • 如果检索了 Google 账号凭据,请使用 ID 令牌进行身份验证 与后端进行通信如果您已选择使用 Nonce 来帮助避免重放 攻击检查后端服务器上的响应值。请参阅 使用 ID 令牌通过后端进行身份验证

Java

public class YourActivity extends AppCompatActivity {

  // ...
  private static final int REQ_ONE_TAP = 2;  // Can be any integer unique to the Activity.
  private boolean showOneTapUI = true;
  // ...

  @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
      super.onActivityResult(requestCode, resultCode, data);

      switch (requestCode) {
          case REQ_ONE_TAP:
              try {
                  SignInCredential credential = oneTapClient.getSignInCredentialFromIntent(data);
                  String idToken = credential.getGoogleIdToken();
                  String username = credential.getId();
                  String password = credential.getPassword();
                  if (idToken !=  null) {
                      // Got an ID token from Google. Use it to authenticate
                      // with your backend.
                      Log.d(TAG, "Got ID token.");
                  } else if (password != null) {
                      // Got a saved username and password. Use them to authenticate
                      // with your backend.
                      Log.d(TAG, "Got password.");
                  }
              } catch (ApiException e) {
                  // ...
              }
              break;
      }
  }
}

Kotlin

class YourActivity : AppCompatActivity() {

    // ...
    private val REQ_ONE_TAP = 2  // Can be any integer unique to the Activity
    private var showOneTapUI = true
    // ...

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        when (requestCode) {
             REQ_ONE_TAP -> {
                try {
                    val credential = oneTapClient.getSignInCredentialFromIntent(data)
                    val idToken = credential.googleIdToken
                    val username = credential.id
                    val password = credential.password
                    when {
                        idToken != null -> {
                            // Got an ID token from Google. Use it to authenticate
                            // with your backend.
                            Log.d(TAG, "Got ID token.")
                        }
                        password != null -> {
                            // Got a saved username and password. Use them to authenticate
                            // with your backend.
                            Log.d(TAG, "Got password.")
                        }
                        else -> {
                            // Shouldn't happen.
                            Log.d(TAG, "No ID token or password!")
                        }
                    }
                } catch (e: ApiException) {
                    // ...
                }
            }
        }
    }
    // ...
}

停止显示一键式界面

如果用户拒绝登录,对 getSignInCredentialFromIntent() 的调用 会抛出状态代码为 CommonStatusCodes.CANCELEDApiException。 出现这种情况时,您应该暂时停用一键登录界面, 不要用重复提示让用户感到厌烦以下示例将完成 方法是在 Activity 上设置一个属性 向用户提供一键登录功能;但您也可以将值 SharedPreferences 或使用其他方法。

请务必自行对一键登录提示实施速率限制。 如果您未设置此政策,并且某位用户连续取消了多条提示,那么一键快捷客户端 在接下来的 24 小时内不会提示用户。

Java

public class YourActivity extends AppCompatActivity {

  // ...
  private static final int REQ_ONE_TAP = 2;  // Can be any integer unique to the Activity.
  private boolean showOneTapUI = true;
  // ...

  @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
      super.onActivityResult(requestCode, resultCode, data);

      switch (requestCode) {
          case REQ_ONE_TAP:
              try {
                  // ...
              } catch (ApiException e) {
                  switch (e.getStatusCode()) {
                      case CommonStatusCodes.CANCELED:
                          Log.d(TAG, "One-tap dialog was closed.");
                          // Don't re-prompt the user.
                          showOneTapUI = false;
                          break;
                      case CommonStatusCodes.NETWORK_ERROR:
                          Log.d(TAG, "One-tap encountered a network error.");
                          // Try again or just ignore.
                          break;
                      default:
                          Log.d(TAG, "Couldn't get credential from result."
                                  + e.getLocalizedMessage());
                          break;
                  }
              }
              break;
      }
  }
}

Kotlin

class YourActivity : AppCompatActivity() {

    // ...
    private val REQ_ONE_TAP = 2  // Can be any integer unique to the Activity
    private var showOneTapUI = true
    // ...

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        when (requestCode) {
            REQ_ONE_TAP -> {
                try {
                    // ...
                } catch (e: ApiException) {
                    when (e.statusCode) {
                        CommonStatusCodes.CANCELED -> {
                            Log.d(TAG, "One-tap dialog was closed.")
                            // Don't re-prompt the user.
                            showOneTapUI = false
                        }
                        CommonStatusCodes.NETWORK_ERROR -> {
                            Log.d(TAG, "One-tap encountered a network error.")
                            // Try again or just ignore.
                        }
                        else -> {
                            Log.d(TAG, "Couldn't get credential from result." +
                                " (${e.localizedMessage})")
                        }
                    }
                }
            }
        }
    }
    // ...
}

5. 处理退出账号

当用户在您的应用中退出账号时,请调用一键快捷客户端的 signOut() 方法。 调用 signOut() 会停用自动登录,直到用户再次登录。

即使您不使用自动登录,此步骤也很重要,因为 可确保当用户退出您的应用时 您使用的 Play 服务 API 也会重置。

后续步骤

如果您将一键式客户端配置为检索 Google 凭据,您的应用 现在可以获取代表用户Google 账号。了解 如何在后端使用这些令牌

如果您支持 Google 登录,还可以使用一键式客户端将 顺畅无碍的账号创建流程