使用 One Tap 登入用戶端要求權限,向使用者要求權限,以擷取先前用於登入應用程式的憑證。這些憑證可以是 Google 帳戶,也可以是使用者透過 Chrome、Android 自動填入或密碼專用 Smart Lock 儲存在 Google 中的使用者名稱密碼組合。
成功擷取憑證後,您可以使用這些憑證讓使用者輕鬆地登入您的應用程式。
如果使用者未儲存任何憑證,系統就不會顯示任何 UI,您也可以提供正常的登出體驗。
哪裡可以使用 One Tap 登入?
如果應用程式需要使用者登入,請在登入畫面上顯示 One Tap UI。即使您已經有「使用 Google 帳戶登入」按鈕,這項功能仍然會很實用,因為 One Tap UI 可設定為只顯示使用者先前用於登入的憑證,所以系統會提醒不常登入他們登入方式的使用者,避免他們不小心使用您的應用程式建立新帳戶。
如果應用程式選用登入功能,建議您在任何具備登入體驗強化體驗的畫面上使用 One Tap 登入。舉例來說,如果使用者可在登出狀態下使用應用程式瀏覽內容,但只能在登入後張貼留言或將商品加入購物車,就屬於 One Tap 登入機制的合理情境。
基於上述原因,選用登入的應用程式也應該在登入畫面上使用 One Tap 登入。
事前準備
- 按照「開始使用 One Tap 登入」一文的說明,設定 Google API 控制台專案和 Android 專案。
- 如果您支援密碼登入,請最佳化應用程式以使用自動填入功能 (或使用密碼專用 Smart Lock),讓使用者可在登入後儲存密碼憑證。
1. 設定 One Tap 登入用戶端
您可以設定 One Tap 登入用戶端,讓使用者透過已儲存的密碼和已儲存的 Google 帳戶或這兩種方式登入。(建議您同時支援這兩種方式,為新使用者啟用一鍵建立帳戶,並盡可能為回訪者啟用自動或輕觸一下的登入功能。)
如果您的應用程式使用密碼登入功能,請使用 setPasswordRequestOptions()
來啟用密碼憑證要求。
如果您的應用程式使用 Google 登入功能,請使用 setGoogleIdTokenRequestOptions()
來啟用並設定 Google ID 權杖要求:
將伺服器用戶端 ID 設為您在 Google API 控制台中建立的 ID。請注意,這裡使用的是伺服器的用戶端 ID,而不是 Android 用戶端 ID。
將用戶端設為依授權帳戶進行篩選。啟用這個選項後,One Tap 用戶端只會提示使用者透過過去使用過的 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. 查看已登入的使用者
如果您的「活動」可能由已登入的使用者或未登入的使用者使用,請先檢查使用者狀態,再顯示 One Tap 登入 UI。
此外,您也應追蹤使用者是否已拒絕使用 One Tap 登入,方法是關閉提示或輕觸提示以外的位置。可以像 Activity 的布林值屬性一樣簡單。(請參閱下方的「停止顯示 One Tap UI」)。
3. 顯示 One Tap 登入 UI
如果使用者未登入,且尚未拒絕使用 One Tap 登入,請呼叫用戶端物件的 beginSignIn()
方法,然後將事件監聽器附加至其傳回的 Task
。應用程式通常會在使用單一活動架構時,透過活動的 onCreate()
方法或在畫面轉換後執行此操作。
如果使用者有任何已儲存的應用程式憑證,One Tap 用戶端就會呼叫成功事件監聽器。在成功監聽器中,從 Task
結果取得待處理意圖,並將其傳遞至 startIntentSenderForResult()
來啟動 One Tap 登入使用者介面。
如果使用者未儲存任何憑證,One Tap 用戶端就會呼叫失敗的事件監聽器。在這種情況下,您不需要採取任何行動:只要繼續提供應用程式的登出體驗即可。不過,如果您支援 One Tap 註冊,可以先從此流程開始,提供順暢的帳戶建立體驗。請參閱「輕觸一下建立新帳戶」。
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. 處理使用者的回應
系統會透過活動的 onActivityResult()
方法,回報使用者對 One Tap 登入提示的回應。如果使用者選擇登入,結果就會是已儲存的憑證。如果使用者拒絕登入,無論是關閉 One Tap UI 或在其外輕觸,結果都會傳回 RESULT_CANCELED
代碼。您的應用程式必須同時處理這兩種可能性。
使用擷取的憑證登入
如果使用者選擇與應用程式分享憑證,您可以將意圖資料從 onActivityResult()
傳遞至 One Tap 用戶端的 getSignInCredentialFromIntent()
方法,藉此擷取憑證。如果使用者向您的應用程式分享 Google 帳戶憑證,憑證就會含有非空值的 googleIdToken
屬性;如果使用者分享已儲存的密碼,憑證就會包含非空值的 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) { // ... } } } } // ... }
停止顯示 One Tap UI
如果使用者拒絕登入,對 getSignInCredentialFromIntent()
的呼叫會擲回含有 CommonStatusCodes.CANCELED
狀態碼的 ApiException
。此時,您應暫時停用 One Tap 登入 UI,以免系統重複提示使用者。以下範例是在 Activity 上設定屬性,利用該屬性判斷是否要為使用者提供 One Tap 登入功能;但是,您也可以將值儲存到 SharedPreferences
或使用其他方法。
請務必自行設定 One Tap 登入提示的頻率限制。 否則,若使用者連續取消數則提示,One Tap 用戶端將不會在接下來 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. 處理登出動作
當使用者登出應用程式時,請呼叫 One Tap 用戶端的 signOut()
方法。呼叫 signOut()
會停用自動登入功能,直到使用者再次登入為止。
即使您並未使用自動登入,這個步驟仍相當重要,因為這樣可確保使用者登出應用程式時,您使用的任何 Play 服務 API 的驗證狀態也會重設。
後續步驟
如果您設定 One Tap 用戶端擷取 Google 憑證,應用程式現在可以取得代表使用者 Google 帳戶的 Google ID 權杖。瞭解如何在後端使用這些符記。
如果您支援 Google 登入,也可以使用 One Tap 用戶端在應用程式中加入順暢的帳戶建立流程。