원탭 로그인 클라이언트를 사용하여 사용자에게 이전에 앱에 로그인할 때 사용한 사용자 인증 정보 중 하나를 검색할 수 있는 권한을 요청합니다. 이러한 사용자 인증 정보는 Google 계정이거나 Chrome, Android 자동 완성 또는 비밀번호 대용 Smart Lock을 사용하여 Google에 저장한 사용자 이름-비밀번호 조합일 수 있습니다.
사용자 인증 정보가 성공적으로 검색되면 이를 사용하여 사용자를 앱에 원활하게 로그인할 수 있습니다.
사용자가 사용자 인증 정보를 저장하지 않았다면 UI가 표시되지 않으며 개발자는 일반적인 로그아웃 환경을 제공할 수 있습니다.
원탭 로그인은 어디에서 사용해야 하나요?
앱에서 사용자에게 로그인을 요구하는 경우 로그인 화면에 원탭 UI를 표시합니다. 이 기능은 이미 'Google 계정으로 로그인' 버튼이 있는 경우에도 유용할 수 있습니다. 원탭 UI는 사용자가 이전에 로그인할 때 사용한 사용자 인증 정보만 표시하도록 구성할 수 있으므로, 이전 로그인 방식을 자주 로그인하지 않은 사용자에게 알림을 제공할 수 있으며, 사용자가 실수로 앱에서 새 계정을 만드는 것을 방지할 수 있습니다.
앱의 로그인이 선택사항인 경우 로그인을 통해 향상된 환경이 제공되는 모든 화면에서 원탭 로그인을 사용하는 것이 좋습니다. 예를 들어 사용자가 로그아웃한 상태에서 앱으로 콘텐츠를 탐색할 수 있지만 로그인한 후에는 댓글을 게시하거나 장바구니에 상품을 추가할 수만 있는 경우 원탭 로그인에 적합한 컨텍스트가 됩니다.
위에 설명된 이유로 로그인 선택적 앱은 로그인 화면에서 원탭 로그인을 사용해야 합니다.
시작하기 전에
- 원탭 로그인 시작하기에 설명된 대로 Google API 콘솔 프로젝트와 Android 프로젝트를 설정합니다.
- 비밀번호 기반 로그인을 지원하는 경우 사용자가 로그인한 후 비밀번호 사용자 인증 정보를 저장할 수 있도록 자동 완성에 맞게 앱을 최적화하거나 비밀번호 대용 Smart Lock을 사용하세요.
1. 원탭 로그인 클라이언트 구성
저장된 비밀번호, 저장된 Google 계정 또는 둘 중 하나를 사용하여 사용자가 로그인하도록 원탭 로그인 클라이언트를 구성할 수 있습니다. 신규 사용자는 원탭 계정 생성을 사용 설정하고 가능한 한 많은 재사용자는 자동 또는 원탭 로그인을 사용할 수 있도록 두 가지를 모두 지원하는 것이 좋습니다.
앱에서 비밀번호 기반 로그인을 사용하는 경우 setPasswordRequestOptions()
를 사용하여 비밀번호 사용자 인증 정보 요청을 사용 설정합니다.
앱에서 Google 로그인을 사용하는 경우 setGoogleIdTokenRequestOptions()
를 사용하여 Google ID 토큰 요청을 사용 설정하고 구성합니다.
서버 클라이언트 ID를 Google API 콘솔에서 만든 ID로 설정합니다. 이는 Android 클라이언트 ID가 아닌 서버의 클라이언트 ID입니다.
승인된 계정별로 필터링하도록 클라이언트를 구성합니다. 이 옵션을 사용 설정하면 원탭 클라이언트는 사용자에게 이전에 이미 사용한 Google 계정으로 앱에 로그인하라는 메시지만 표시합니다. 이렇게 하면 사용자가 이미 계정을 보유하고 있는지, 어떤 Google 계정을 사용했는지 확신할 수 없을 때 로그인하는 데 도움이 되며, 사용자가 실수로 앱에서 새 계정을 만드는 것을 방지할 수 있습니다.
가능한 경우 자동으로 사용자가 로그인되도록 하려면
setAutoSelectEnabled()
로 이 기능을 사용 설정하세요. 다음 기준이 충족되면 자동 로그인이 가능합니다.- 앱에 저장된 사용자의 사용자 인증 정보가 정확히 한 개입니다. 즉, 저장된 비밀번호 또는 저장된 Google 계정이 한 개입니다.
- 사용자가 Google 계정 설정에서 자동 로그인을 사용 중지하지 않았습니다.
선택사항이지만 로그인 보안을 강화하고 재생 공격을 방지하려면 nonce를 사용하는 것이 좋습니다. 각 요청에 nonce를 포함하려면 setNonce를 사용합니다. 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. 로그인한 사용자 확인
로그인한 사용자나 로그아웃한 사용자가 활동을 사용할 수 있는 경우 원탭 로그인 UI를 표시하기 전에 사용자의 상태를 확인합니다.
또한 프롬프트를 닫거나 프롬프트 외부를 탭하여 사용자가 원탭 로그인 사용을 이미 거부했는지 추적해야 합니다. 이는 Activity의 불리언 속성만큼 간단할 수 있습니다. 아래의 원탭 UI 표시 중지하기를 참고하세요.
3. 원탭 로그인 UI 표시
사용자가 로그인하지 않았고 원탭 로그인 사용을 거부하지 않았다면 클라이언트 객체의 beginSignIn()
메서드를 호출하고 반환되는 Task
에 리스너를 연결합니다. 앱은 일반적으로 활동의 onCreate()
메서드에서 이 작업을 실행하거나 단일 활동 아키텍처를 사용할 때 화면 전환 후에 실행합니다.
원탭 클라이언트는 사용자에게 저장된 앱 사용자 인증 정보가 있는 경우 성공 리스너를 호출합니다. 성공 리스너의 Task
결과에서 대기 중인 인텐트를 가져와 startIntentSenderForResult()
에 전달하여 원탭 로그인 UI를 시작합니다.
사용자에게 저장된 사용자 인증 정보가 없는 경우 원탭 클라이언트는 실패 리스너를 호출합니다. 이 경우 별도의 조치가 필요하지 않습니다. 앱의 로그아웃 환경을 계속 표시하기만 하면 됩니다. 그러나 원탭 가입을 지원하는 경우 여기에서 해당 과정을 시작하여 원활한 계정 생성 환경을 만들 수 있습니다. 탭 한 번으로 새 계정 만들기를 참조하세요.
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()
메서드를 사용하여 앱에 보고됩니다. 사용자가 로그인을 선택한 경우 저장된 사용자 인증 정보가 결과에 표시됩니다. 사용자가 원탭 UI를 닫거나 외부를 탭하여 로그인을 거부하면 RESULT_CANCELED
코드와 함께 결과가 반환됩니다. 앱은 두 가지 가능성을 모두 처리해야 합니다.
가져온 사용자 인증 정보로 로그인
사용자가 앱과 사용자 인증 정보를 공유하기로 선택한 경우 개발자는 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) { // ... } } } } // ... }
원탭 UI 표시 중지
사용자가 로그인을 거부한 경우 getSignInCredentialFromIntent()
를 호출하면 CommonStatusCodes.CANCELED
상태 코드와 함께 ApiException
이 발생합니다.
이 경우 원탭 로그인 UI를 일시적으로 사용 중지하여 반복적인 메시지로 사용자를 짜증나게 하지 않도록 해야 합니다. 다음 예에서는 활동에 원탭 로그인을 제공할지 결정하는 데 사용하는 속성을 활동에 설정하여 이를 실행합니다. 그러나 값을 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 로그인을 지원하는 경우 원탭 클라이언트를 사용하여 앱에 원활한 계정 생성 흐름을 추가할 수도 있습니다.