원탭 로그인 클라이언트를 사용하여 사용자가 이전에 앱에 로그인하는 데 사용한 사용자 인증 정보 중 하나를 검색할 수 있는 권한을 요청합니다. 이러한 사용자 인증 정보는 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()
로 이 기능을 사용 설정합니다. 다음 기준이 충족되면 자동 로그인이 가능합니다.- 사용자가 앱에 저장된 사용자 인증 정보가 정확히 1개 있습니다. 저장된 비밀번호 1개 또는 저장된 Google 계정 1개입니다.
- 사용자가 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를 표시하기 전에 사용자의 상태를 확인하세요.
또한 사용자가 메시지를 닫거나 탭 바깥을 탭하여 원탭 로그인 사용을 이미 거부했는지 추적합니다. 활동의 불리언 속성만큼 간단할 수 있습니다. (아래 원탭 UI 표시 중지하기를 참고하세요.)
3. 원탭 로그인 UI 표시
사용자가 로그인하지 않았고 원탭 로그인 사용을 아직 거부하지 않은 경우 클라이언트 객체의 beginSignIn()
메서드를 호출하고 반환된 Task
에 리스너를 연결합니다. 앱은 일반적으로 Activity의 onCreate()
메서드에서 또는 단일 Activity 아키텍처를 사용할 때 화면 전환 후에 이 작업을 실행합니다.
사용자가 앱에 저장된 사용자 인증 정보가 있으면 원탭 클라이언트에서 성공 리스너를 호출합니다. 성공 리스너의 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를 닫거나 외부를 탭하여 로그인을 거부하면 코드가 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를 일시적으로 사용 중지하여 반복적인 메시지로 사용자를 성가시게 하지 않아야 합니다. 다음 예에서는 활동에서 사용자에게 속성을 설정하여 원탭 로그인을 제공할지 여부를 결정하는 방식으로 이를 실행합니다. 그러나 값을 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 로그인을 지원하는 경우 원탭 클라이언트를 사용하여 앱에 원활한 계정 생성 절차를 추가할 수도 있습니다.