使用一键登录客户端向用户请求权限,以检索他们之前用于登录应用的凭据。这些凭据可以是 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。如需了解有关生成 Nonce 的建议和其他详细信息,请参阅 SafetyNet 的获取 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
结果获取待处理 intent 并将其传递给 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()
方法来检索凭据。如果用户与您的应用共享了 Google 帐号凭据,则该凭据将具有非 null googleIdToken
属性;如果用户分享了已保存的密码,则该凭据将具有非 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()
将抛出 ApiException
及 CommonStatusCodes.CANCELED
状态代码。发生这种情况时,您应暂时停用一键登录界面,以免重复提示会令用户感到厌烦。以下示例通过在 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 ID 令牌。了解如何在后端使用这些令牌。
如果您支持 Google 登录,则还可以使用一键快捷功能客户端向应用添加顺畅的帐号创建流程。