원탭 로그인 클라이언트를 사용하여 사용자가 이전에 앱에 로그인하는 데 사용한 사용자 인증 정보 중 하나를 검색할 수 있는 권한을 사용자에게 요청합니다. 이러한 사용자 인증 정보는 Chrome, Android 자동 완성 또는 비밀번호용 스마트 잠금을 사용하여 Google에 저장한 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를 사용하는 것이 좋습니다. setNonce를 사용하여 각 요청에 nonce를 포함합니다. nonce 생성에 관한 제안사항과 추가 세부정보는 SafetyNet의 nonce 가져오기 섹션을 참고하세요.
자바
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를 시작합니다.
저장된 사용자 인증 정보가 없는 경우 원탭 클라이언트는 실패 리스너를 호출합니다. 이 경우 별도의 조치를 취하지 않아도 됩니다. 앱의 로그아웃 환경을 계속 표시하면 됩니다. 하지만 원탭 가입을 지원하는 경우 여기에서 흐름을 시작하여 원활한 계정 생성 환경을 제공할 수 있습니다. 탭 한 번으로 새 계정 만들기를 참고하세요.
자바
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를 닫거나 UI 외부를 탭하여 로그인을 거부하면 결과가 RESULT_CANCELED
코드와 함께 반환됩니다. 앱은 두 가지 가능성 모두를 처리해야 합니다.
검색된 사용자 인증 정보로 로그인
사용자가 앱과 사용자 인증 정보를 공유하도록 선택한 경우 onActivityResult()
에서 원탭 클라이언트의 getSignInCredentialFromIntent()
메서드로 인텐트 데이터를 전달하여 사용자 인증 정보를 가져올 수 있습니다. 사용자가 앱과 Google 계정 사용자 인증 정보를 공유한 경우 사용자 인증 정보에 null이 아닌 googleIdToken
속성이 있고, 사용자가 저장된 비밀번호를 공유한 경우 null이 아닌 password
속성이 있습니다.
사용자 인증 정보를 사용하여 앱의 백엔드에 인증합니다.
- 사용자 이름과 비밀번호 쌍이 검색된 경우 사용자가 직접 입력한 것처럼 로그인하는 데 사용합니다.
Google 계정 사용자 인증 정보가 검색된 경우 ID 토큰을 사용하여 백엔드와 인증합니다. 재전송 공격을 방지하기 위해 nonce를 사용하기로 선택한 경우 백엔드 서버에서 응답 값을 확인하세요. ID 토큰을 사용하여 백엔드와 인증을 참고하세요.
자바
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를 일시적으로 사용 중지해야 합니다. 다음 예에서는 사용자에게 원탭 로그인을 제공할지 결정하는 데 사용하는 Activity의 속성을 설정하여 이를 실행합니다. 하지만 SharedPreferences
에 값을 저장하거나 다른 메서드를 사용할 수도 있습니다.
원탭 로그인 메시지의 자체 비율 제한을 구현하는 것이 중요합니다. 이렇게 하지 않고 사용자가 연속으로 여러 메시지를 취소하면 원탭 클라이언트는 다음 24시간 동안 사용자에게 메시지를 표시하지 않습니다.
자바
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 로그인을 지원하는 경우 원탭 클라이언트를 사용하여 앱에 원활한 계정 생성 흐름을 추가할 수도 있습니다.