使用 One Tap 登入用戶端,向使用者索取權限,以便擷取他們先前用於登入應用程式的憑證。這些憑證可以是 Google 帳戶,也可以是使用者透過 Chrome、Android 自動填入或 Smart Lock for Passwords 儲存至 Google 的使用者名稱和密碼組合。
憑證擷取成功後,您就可以使用這些憑證,讓使用者無縫登入應用程式。
如果使用者未儲存任何憑證,系統就不會顯示 UI,您可以提供正常的登出體驗。
在何處使用 One Tap 登入?
如果您的應用程式要求使用者登入,請在登入畫面上顯示 One Tap UI。即使您已提供「使用 Google 帳戶登入」按鈕,這項功能仍相當實用:One Tap UI 可設定為只顯示使用者先前用於登入的憑證,提醒不常登入的使用者上次登入的方式,並避免他們誤用應用程式建立新帳戶。
如果應用程式可讓使用者選擇是否登入,請考慮在任何可透過登入提升體驗的畫面上使用 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 登入功能,方法是關閉提示或輕觸提示以外的區域。這可以像活動的布林值屬性一樣簡單。(請參閱下方的「停止顯示 One Tap UI」)。
3. 顯示 One Tap 登入 UI
如果使用者未登入,且尚未拒絕使用 One Tap 登入功能,請呼叫用戶端物件的 beginSignIn()
方法,並將事件監聽器附加至所傳回的 Task
。應用程式通常會在 Activity 的 onCreate()
方法中,或在使用單一活動架構時在畫面轉換後執行這項操作。
如果使用者有任何已儲存的應用程式憑證,One Tap 用戶端就會呼叫成功事件監聽器。在成功事件監聽器中,請從 Task
結果取得待處理 Intent,並將其傳遞至 startIntentSenderForResult()
,以啟動 One Tap 登入 UI。
如果使用者沒有任何已儲存的憑證,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. 處理使用者的回應
使用者對 One Tap 登入提示的回應,會透過活動的 onActivityResult()
方法回報給應用程式。如果使用者選擇登入,結果會是已儲存的憑證。如果使用者拒絕登入 (例如關閉 One Tap UI 或輕觸 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()
的呼叫會擲回 ApiException
,並帶有 CommonStatusCodes.CANCELED
狀態碼。發生這種情況時,您應暫時停用 One Tap 登入 UI,以免使用者因不斷收到提示而感到厭煩。以下範例會在活動上設定屬性,用於判斷是否要為使用者提供 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 用戶端,在應用程式中新增無縫帳戶建立流程。