이 페이지에서는 복원 키를 만들고, 복원 키로 로그인하고, 복원 키를 삭제하는 방법을 설명합니다.
Android 기술
GitHub에서 보기사용자 인증 정보 복원
android skills add restore-credentials버전 호환성
인증 관리자의 사용자 인증 정보 복원은 Android 9(API 수준 28) 이상, Google Play 서비스(GMS) 코어 버전 242200000(v24.22) 이상, androidx.credentials 라이브러리 버전 1.5.0 이상을 실행하는 기기에서 작동합니다. Google Play 서비스가 없거나 이전 GMS 버전이 있는 기기에서는 앱이 표준 로그인으로 원활하게 대체되도록 CreateCredentialProviderConfigurationException 및 GetCredentialProviderConfigurationException를 포착합니다.
기본 요건
패스키용 서버와 유사한 WebAuthn 신뢰 당사자 서버를 구성합니다. 패스키를 사용하여 인증을 처리하도록 설정된 서버가 이미 있는 경우 복원 키에 동일한 WebAuthn 등록 및 어설션 엔드포인트를 재사용할 수 있습니다.
종속 항목
앱 모듈의 build.gradle 파일에 다음 종속 항목을 추가합니다.
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.7.0-alpha02") implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02") }
Groovy
dependencies { implementation "androidx.credentials:credentials:1.7.0-alpha02" implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02" }
사용자 인증 정보 복원은 androidx.credentials 라이브러리의 버전 1.5.0 이상에서 사용할 수 있습니다. 하지만 가능하면 종속 항목의 최신 안정화 버전을 사용하는 것이 좋습니다.
android:allowBackup가 사용자 인증 정보 복원에 미치는 영향
android:allowBackup 매니페스트 속성은 앱 데이터 백업을 제어하며, 이는 다음과 같은 세 가지 방식으로 사용자 인증 정보 복원과 상호작용합니다.
- 복원 키 전송(
allowBackup독립적): Google Play 서비스는android:allowBackup가true인지false인지와 관계없이 엔드 투 엔드 암호화 클라우드 백업 또는 로컬 기기 간(D2D) 전송을 사용하여RestoreCredential키 쌍을 전송합니다. D2D 전송의 경우 복원 키는 항상 전송됩니다. 전송되지 않는 유일한 조건은 사용자가 Android 설정에서 클라우드 백업을 선택 해제한 경우 클라우드 백업 중에 전송되지 않습니다. BackupAgent의 백그라운드 복원(android:allowBackup="true"필요): 사용자가 새 기기에서 앱을 열기 전에 백그라운드에서 자동으로 로그인하는 기능(BackupAgent.onRestoreFinished())은 매니페스트에android:allowBackup="true"및android:backupAgent가 구성된 경우에만 실행됩니다.android:allowBackup="false"인 경우 사용자가 기본Activity를 처음 실행할 때 세션을 복원합니다. 두 가지 접근 방식을 모두 구현하여 복원을 더 강력하게 만들 수도 있습니다.- Android 스튜디오 클라우드 백업 테스트(
android:allowBackup="true"필요): Android 스튜디오의 백업 및 복원 도구에서 클라우드 백업 유형을 테스트하려면android:allowBackup="true"가 필요하지만 기기 간 테스트는allowBackup와 관계없이 작동합니다.
개요
사용자 인증 정보 복원은 사용자의 이전 기기와 새 기기에서 3단계 수명 주기를 따릅니다.
- 이전 기기에서 복원 키 만들기: 사용자가 로그인한 후(또는 사용자가 이미 로그인되어 있고 로컬 저장소에 복원 키가 기록되어 있지 않은 경우 앱 시작 시) 신뢰 당사자 서버에서 생성 옵션을 가져와
credentialManager.createCredential()를 호출합니다. - 새 기기에서 복원 키로 로그인: 2단계 전략을 사용하여 전송된
RestoreCredential를 가져옵니다. 먼저BackupAgent.onRestoreFinished()에서 자동으로(android:allowBackup="true"인 경우) 백그라운드 동기화 및 푸시 알림이 즉시 재개되고, 두 번째로 앱을 처음 실행할 때 런처Activity.onCreate()에서 대체로 가져옵니다. - 로그아웃 시 복원 키 삭제: 사용자가 로그아웃하거나 서버에서 세션이 무효화되면
ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)를 사용하여credentialManager.clearCredentialState()를 호출하고 로컬 동기화 플래그를 지웁니다.
복원 키 만들기
앱은 활성 사용자에게 복원 키가 생성되도록 사용자가 로그인하는 모든 케이스를 처리해야 합니다. 다음 시나리오에서 복원 키를 만듭니다.
- 사용자가 로그인되어 있고 복원 키가 아직 생성되지 않은 경우(예: 기본
Activity의onCreate()메서드) - 사용자가 로그인하거나 새 계정 등록 과정을 완료하는 경우
앱을 실행할 때마다 createCredential()를 호출하지 않으려면 로컬 저장소 내에 has_synced_restore_credential과 같은 로컬 동기화 플래그를 저장합니다. data_extraction_rules.xml에서 해당 파일이 제외되지 않는 한 이 플래그를 기본 백업된 SharedPreferences 또는 DataStore에 저장하지 마세요. 그렇지 않으면 자동 백업이 has_synced_restore_credential = true를 새 기기에 복사하여 새 기기가 자체 복원 키를 생성하거나 순환하지 못하게 합니다.
인증 관리자 인스턴스화
앱의 활동 컨텍스트를 사용하여 CredentialManager 객체를 인스턴스화합니다.
// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)
앱 서버에서 사용자 인증 정보 생성 옵션 가져오기
앱 서버에서 FIDO 호환 라이브러리를 사용하여 복원 사용자 인증 정보를 만드는 데 필요한 PublicKeyCredentialCreationOptions JSON을 클라이언트 앱에 전송합니다. 서버 측 구현에 관한 자세한 내용은 서버 측 가이드를 참고하세요.
생성 옵션 JSON은 패스키 등록과 동일한 구조를 따릅니다.
{
"challenge": "<base64url-encoded challenge>",
"rp": {
"name": "<relying party name>",
"id": "<relying party host name>"
},
"user": {
"id": "<base64url-encoded user ID>",
"name": "<user name>",
"displayName": "<user display name>"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
}
],
"attestation": "none",
"excludeCredentials": [
{
"id": "<base64url-encoded credential ID to exclude>",
"type": "public-key"
}
],
"authenticatorSelection": {
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
생성 옵션 JSON이 다음 요구사항을 충족하는지 확인합니다.
challenge및user.id: 두 값을 패딩되지 않은 Base64URL 문자열로 인코딩합니다(+를-로,/를_로 바꾸고 후행=삭제). 표준 Base64 문자 또는 원시 정수 ID는CreateRestoreCredentialDomException또는IllegalArgumentException를 발생시킵니다.rp.id: 신뢰 당사자의 도메인으로 설정합니다(예:example.com).pubKeyCredParams:{"type": "public-key", "alg": -7}(ES256) 및{"type": "public-key", "alg": -257}(RS256)를 포함합니다.
복원 키 만들기
서버에서 공개 키 생성 옵션 JSON을 받은 후 JSON을 CreateRestoreCredentialRequest 객체로 래핑하고 CredentialManager 객체에서 createCredential()를 호출합니다.
// createRestoreRequest contains the details sent by the server
val response = credentialManager.createCredential(context, createRestoreRequest)
코드에 관한 핵심 사항
CreateRestoreCredentialRequest객체에는 다음 필드가 포함됩니다.requestJson:PublicKeyCredentialCreationOptionsJSON의 Web Authentication API 형식으로 앱 서버에서 전송한 사용자 인증 정보 생성 옵션입니다.isCloudBackupEnabled: 복원 키를 클라우드에 백업해야 하는지 결정하는Boolean필드입니다. 기본적으로 이 플래그는true입니다. 이 필드에는 다음 값이 포함됩니다.true: (권장) 이 값은 사용자가 Google 백업 및 엔드 투 엔드 암호화(예: 화면 잠금)를 사용 설정한 경우 클라우드에 복원 키를 백업할 수 있도록 합니다.false: 이 값은 키를 클라우드가 아닌 로컬에 저장합니다. 사용자가 클라우드에서 복원하도록 선택하면 새 기기에서 키를 사용할 수 없지만 로컬 케이블 또는 Wi-Fi 기기 간(D2D) 설정 중에는 키가 계속 전송됩니다.
사용자 인증 정보 생성 응답 처리
Credential Manager API는 CreateRestoreCredentialResponse 유형의 응답을 반환합니다. 이 응답에는 공개 키 사용자 인증 정보 등록 응답이 JSON 형식으로 포함됩니다.
앱에서 신뢰 당사자 서버로 response.registrationResponseJson를 전송하여 사용자 계정과 함께 공개 키를 확인하고 저장한 다음 로컬 저장소에 로컬 동기화 마커를 기록합니다. 서버 측 확인에 관한 자세한 내용은 패스키 가이드를 참고하세요.
복원 키 생성 프로세스 중에 다음 예외를 처리합니다.
CreateRestoreCredentialDomException: 이 예외는requestJson가 잘못되었고PublicKeyCredentialCreationOptionsJSON의 WebAuthn 형식을 따르지 않는 경우에 발생합니다.E2eeUnavailableException: 이 예외는isCloudBackupEnabled가true이지만 사용자의 기기에 데이터 백업 또는 엔드 투 엔드 암호화(예: 화면 잠금)가 없는 경우에 발생합니다.
클라우드 E2EE를 사용할 수 없는 경우에도 로컬 기기 간(D2D) 전송을 위해 복원 사용자 인증 정보가 생성되도록 하려면E2eeUnavailableException를 포착하고CreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false)를 사용하여createCredential()를 즉시 다시 시도합니다.IllegalArgumentException: 이 예외는requestJson가 비어 있거나, 유효한 JSON이 아니거나, WebAuthn 사양을 준수하는 유효한 Base64URL로 인코딩된user.id가 없는 경우에 발생합니다.
복원 키로 로그인하기
사용자 인증 정보 복원을 사용하여 기기 설정 과정에서 사용자를 자동으로 로그인 처리합니다.
앱 서버에서 사용자 인증 정보 검색 옵션 가져오기
새 기기에서 getCredential()를 호출하기 전에 앱 서버에서 WebAuthn PublicKeyCredentialRequestOptions JSON 챌린지를 요청합니다. 새 기기의 앱은 아직 어떤 사용자가 로그인하는지 알지 못하므로 이 엔드포인트는 인증되지 않아야 합니다(사용자 없는 검색). 관련 서버 안내는 패스키로 로그인 및 서버 측 인증 가이드를 참고하세요.
검색 옵션 JSON의 구조는 다음과 같습니다.
{
"challenge": "<your app challenge>",
"allowCredentials": [],
"rpId": "<your app server domain>"
}
다음과 같이 서버에서 검색 옵션 JSON을 구성합니다.
allowCredentials: 복원 전에는 사용자 계정을 알 수 없으므로 이 필드를 생략하거나 빈 배열([])을 전달합니다.getCredential()가 반환되면 서버는response.userHandle필드(생성 중에 등록된user.id와 일치)에서 사용자를 식별하고id(credentialId)로 공개 키를 조회합니다.userVerification:"discouraged"(또는"preferred")로 설정합니다. 사용자 인증 정보 복원은 생체 인식 또는 화면 잠금 UI 없이 백그라운드에서 실행되므로 Android는"required"를"discouraged"로 재정의하고 서버에 반환된 인증자 데이터에는 사용자 인증(uv) 플래그가false로 설정됩니다. 복원 키 어설션에uv = false를 허용하도록 WebAuthn 서버 검증자를 구성합니다.
복원 키 가져오기
새 기기에서 복원 키를 가져오려면 서버의 requestJson를 GetCredentialRequest 내의 GetRestoreCredentialOption로 래핑하고 CredentialManager 객체에서 getCredential()를 호출합니다.
2단계 복원 전략을 구현하여 백그라운드 앱 데이터 복원이 실행되는지 여부와 관계없이 사용자가 로그인되도록 합니다.
- Tier 1(
BackupAgent.onRestoreFinished()의 백그라운드 복원): 앱 데이터 백업이 사용 설정된 경우(android:allowBackup="true")BackupAgent를 서브클래스로 분류하고onRestoreFinished()를 재정의합니다(기존 키-값 백업에만 호출되는onRestore()를 사용하지 않음).onRestoreFinished()는 동기식이고getCredential()는 Kotlinsuspend함수이므로runBlocking(Dispatchers.IO)를 사용하여 호출을 연결합니다. 복원 키가 없으면NoCredentialException를 자동으로 포착합니다. - Tier 2(런처
Activity.onCreate()의 포그라운드 대체): 로컬 세션이 없는 첫 번째 앱 실행 시 로그인 화면을 표시하기 전에lifecycleScope에서getCredential()를 호출합니다. 이렇게 하면android:allowBackup="false"가 있는 앱을 처리하고onRestoreFinished()가 실행될 때 기기가 오프라인 상태인 경우 복구됩니다.
이미 android:allowBackup="true"로 자동 백업을 사용하는 앱에 새 BackupAgent를 추가하는 경우 AndroidManifest.xml의 <application> 요소에 android:fullBackupOnly="true"도 설정하여 Android가 기존 키-값 백업으로 전환하는 대신 자동 백업을 계속 사용하도록 합니다.
// Fetch the options required to get the restore key
val authenticationJson = fetchAuthenticationJson()
// Create the GetRestoreCredentialRequest object
val options = GetRestoreCredentialOption(authenticationJson)
val getRequest = GetCredentialRequest(listOf(options))
val response = credentialManager.getCredential(context, getRequest)
// Type-check and extract the restore credential
val credential = response.credential as RestoreCredential
Credential Manager API는 credential 속성이 authenticationResponseJson를 포함하는 RestoreCredential(PublicKeyCredential 아님)인 GetCredentialResponse를 반환합니다.
로그인 응답 처리
앱에서 restoreCredential.authenticationResponseJson를 신뢰 당사자 서버로 전송하여 암호화 어설션을 확인하고 사용자의 새 세션 토큰을 발급합니다. 서버 측 확인에 관한 자세한 내용은 패스키로 로그인을 참고하세요.
복원 키의 백엔드 스토리지 및 수명 주기 정책을 설계할 때는 다음 가이드라인을 따르세요.
- 사용자 패스키와 복원 키 구분: 데이터베이스에서 복원 키에 태그를 지정하여 사용자 대상 패스키 관리 화면에서 숨기고 사용자 인증(
uv = true) 검사에서 제외합니다. - 사용자당 여러 활성 기기 지원: 사용자는 여러 기기(예: 휴대전화, 태블릿)를 사용할 수 있습니다. 단일 행을 덮어쓰는 대신 사용자 계정당 여러 개의 활성 복원 키를 허용합니다(예: 기기 또는 설치 ID로 키 지정).
- 적절한 TTL 설정 및 분리된 키 정리: 앱을 제거하거나 앱 데이터를 삭제하면 서버에 알리지 않고 로컬 복원 키가 삭제됩니다. 기기 업그레이드 기간 동안 유지될 수 있도록 복원 키를 충분히 오래 보관하고(사용자가 전송 후 이전 기기에서 로그아웃하더라도) 비활성 TTL 또는 사용자별 키 제한에 따라 오래된 키를 정리합니다.
복원 키 삭제
인증 관리자는 스테이트리스(Stateless)이며 사용자 활동을 인식하지 못하므로 사용 후 복원 키를 자동으로 삭제하지 않습니다. 복원 키를 삭제하려면 clearCredentialState() 메서드를 호출합니다. 보안을 위해 사용자가 로그아웃할 때마다 키를 삭제하여 다음 앱 실행 시 자동으로 다시 로그인되지 않도록 합니다.
복원 키는 다음과 같은 경우에만 삭제됩니다.
- 시스템 수준 작업: 사용자가 앱을 제거하거나 앱 데이터를 삭제합니다.
- 앱 수준 호출: 앱 코드에서 사용자 로그아웃을 처리할 때
clearCredentialState()를 호출하여 프로그래매틱 방식으로 키를 삭제합니다.
기본 ClearCredentialStateRequest()는 표준 세션 상태만 지우고 복원 키는 그대로 유지하므로 로그아웃 시 이중 삭제를 실행합니다.
credentialManager.clearCredentialState(ClearCredentialStateRequest())를 호출하여 활성 사용자 인증 정보 제공업체 세션 상태를 지웁니다.ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)로credentialManager.clearCredentialState()를 호출하여 저장된RestoreCredential를 삭제합니다.- 로컬 저장소에서 로컬
has_synced_restore_credential마커를 삭제합니다.
// Create a ClearCredentialStateRequest object
val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)
// When the user logs out, delete the restore key
val response = credentialManager.clearCredentialState(clearRequest)
다음 단계
- 복원 사용자 인증 정보 테스트: Android 스튜디오 또는
adb shell bmgr를 사용하여 클라우드 및 기기 간(D2D) 복원 흐름을 테스트합니다. - 자동 백업 및 복원 테스트:
BackupRestoreController를 사용하여 JUnit 5 호스트 테스트를 작성하여 CI에서CLOUD_ENCRYPTED및DEVICE_TO_DEVICE복원을 확인합니다.