사용자 인증 정보 관리자의 사용자 인증 정보 전송 API를 사용하면 사용자 인증 정보를 사용자 인증 정보 제공업체 간에 동일한 기기에서 안전하게 전송할 수 있습니다. 이 가이드에서는 Android의 사용자 인증 정보 제공업체가
에서 제공하는 API와 통합하는 방법을 자세히 설명합니다.
androidx.credentials:providerevents 라이브러리 이 기능은
표준화된 FIDO 사용자 인증 정보 교환 형식 (CXF)을 사용하여
비밀번호, 패스키, 주소 정보, 맞춤 필드를 지원합니다.
핵심 개념
사용자 인증 정보 전송 프레임워크는 Android OS 또는 인증되지 않은 앱에 원시 사용자 인증 정보를 노출하지 않고 동일한 기기에서 피어 투 피어 사용자 인증 정보 전송을 지원합니다.
프레임워크는 두 가지 기본 역할을 정의합니다.
- 내보내기 (소스 제공업체): 현재 사용자 인증 정보를 보유하고 있는 사용자 인증 정보 제공업체입니다. 사용자가 선택하면 사용 가능한 내보낼 수 있는
계정 (
ExportEntry)에 관한 메타데이터를 시스템에 사전 등록하고 전송 요청에 응답합니다. - 가져오기 (클라이언트 제공업체 또는 설정 마법사): 수신할 수 있는 사용자 인증 정보 및
확장 프로그램 유형을 지정하는 가져오기 요청
(
ImportCredentialsRequest)을 시작하는 사용자 인증 정보 제공업체 또는 설정 마법사입니다.
Android 버전 호환성
사용자 인증 정보 전송 API는 Android 8 (API 수준 26) 이상을 실행하는 기기에서 작동합니다.
종속 항목 추가
모듈의
build.gradle 또는 build.gradle.kts에 androidx.credentials:providerevents 종속 항목을 추가합니다.
dependencies {
implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}
필수 클래스 인스턴스화
필수 ProviderEventsManager의 인스턴스를 만듭니다.
val providerEventsManager = ProviderEventsManager.create(context)
내보내기 구현
사용자가 앱에서 기기의 다른 사용자 인증 정보 제공업체로 사용자 인증 정보를 내보낼 수 있도록 하려면 내보내기 역할을 구현합니다.
내보내기 항목 등록
사용자 인증 정보 제공업체가 변경되면 (예: 사용자가 로그인하거나, 사용자 인증 정보를 추가하거나, 계정을 수정함) ProviderEventsManager.registerExport()를 사용하여 ExportEntry 항목을 등록하거나 업데이트합니다.
각 ExportEntry에는 다음이 필요합니다.
id: 이 내보내기 항목을 고유하게 나타내는 비밀의 임의로 생성된 문자열 식별자입니다. 이 ID는 안전하게 유지해야 합니다. 나중에 수신되는 전송 요청을 확인하는 데 필요합니다 .accountDisplayName: 선택적 계정 라벨 (예:"Personal Account")userDisplayName: 사용자의 기본 식별자 (예:"alice@example.com")icon: 제공업체 또는 계정을 나타내는Bitmap아이콘 (라이브러리에서 32x32 PNG로 자동 확장됨)supportedCredentialTypes: 이 항목이 보유하는 유형을 나타내는CredentialTypes의 문자 상수 집합
suspend fun registerMyProviderForExport( providerEventsManager: ProviderEventsManager, providerIcon: Bitmap, // Randomly generated and stored in encrypted storage secretEntryId: String ) { val entry = ExportEntry( id = secretEntryId, accountDisplayName = "MyProvider Personal", userDisplayName = "alice@example.com", icon = providerIcon, supportedCredentialTypes = setOf( CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, // Passwords CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, // Passkeys CredentialTypes.CREDENTIAL_TYPE_ADDRESS, CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD ) ) // RegisterExportRequest.create() attaches the default WASM matcher from assets val request = RegisterExportRequest.create(context, listOf(entry)) try { val response = providerEventsManager.registerExport(request) // Registration successful } catch (e: Exception) { // Handle registration exceptions (e.g., RegisterExportProviderConfigurationException) } }
매니페스트 파일에서 내보내기 활동 선언
사용자가 시스템 선택기 UI에서 ExportEntry를 선택하면 시스템에서 지정된 처리 Activity를 실행합니다. 필수 인텐트 작업 및 콘텐츠 URI 스키마를 사용하여 이 활동을 선언합니다.
<activity
android:name="com.example.CredentialExportActivity"
android:exported="true"
android:label="@string/export_activity_label">
<intent-filter>
// This intent action is required for Credential Manager to invoke this activity
<action android:name="androidx.identitycredentials.action.IMPORT_CREDENTIALS" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
</intent-filter>
</activity>
활동에서 전송 인텐트 처리
CredentialExportActivity에서 IntentHandler.retrieveProviderImportCredentialsRequest(intent)를 사용하여 요청을 파싱하고, 호출 앱과 credId를 확인하고, 필요한 생체 인증을 실행하고, 제공된 콘텐츠 URI에 FIDO CXF 페이로드를 다시 씁니다.
class CredentialExportActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 1. Extract the transfer request from the incoming Intent val request: ProviderImportCredentialsRequest? = IntentHandler.retrieveProviderImportCredentialsRequest(intent) if (request == null) { finishWithError() return } // 2. Validate CallingAppInfo and secret `credId` val callingAppPackage = request.callingAppInfo.packageName val receivedCredId = request.credId if (!verifySecretEntryId(receivedCredId) || !isTrustedImporter(callingAppPackage)) { // Secret ID mismatch or untrusted caller -> abort sendExceptionAndFinish(ImportCredentialsNoExportOptionException("Unauthorized request")) return } // 3. Optional: Prompt user for Biometric / PIN authentication before exporting authenticateUserThenExport(request) } private fun authenticateUserThenExport(request: ProviderImportCredentialsRequest) { // ... Biometric prompt logic ... // Once authenticated, generate the FIDO CXF JSON string matching the requested types val cxfJsonPayload = buildFidoCxfJsonPayload( requestedTypes = request.request.credentialTypes, requestedExtensions = request.request.knownExtensions ) val response = ImportCredentialsResponse(cxfJsonPayload) // 4. Write the JSON payload to the Content URI and set Activity result IntentHandler.setImportCredentialsResponse( context = this, uri = request.uri, intent = intent, response = response ) setResult(Activity.RESULT_OK, intent) finish() } private fun sendExceptionAndFinish(exception: androidx.credentials.providerevents.exception.ImportCredentialsException) { IntentHandler.setImportCredentialsException(intent, exception) setResult(Activity.RESULT_OK, intent) finish() } private fun verifySecretEntryId(credentialId: String): Boolean { // Check if credentialId matches what you stored when calling RegisterExportRequest return credentialId == getStoredSecretEntryId() } private fun isTrustedImporter(packageName: String): Boolean { // Implement any specific allowlisting / caller checks if required return true } private fun finishWithError() { setResult(Activity.RESULT_CANCELED) finish() } }
내보내기 등록 및 삭제 예외
이 모듈의 모든 예외는
ImportCredentialsException, RegisterExportException 또는
ClearExportException의 서브클래스입니다.
RegisterExportProviderConfigurationException또는ClearExportProviderConfigurationException: 제공업체 설정 문제 (예:supportedCredentialTypes의 빈ExportEntry또는 지원되지 않는 OS 계층에서 호출)로 인해 등록 또는 삭제가 실패할 때 발생합니다.RegisterExportUnknownErrorException또는ClearExportUnknownErrorException: 내보내기 레지스트리를 업데이트하는 중에 분류되지 않은 시스템 또는 저장소 오류가 발생했습니다.
가져오기 구현
앱으로 사용자 인증 정보를 가져오려면 (예: 온보딩 또는 제공업체
가져오기 중) 가져오기 역할을 구현하고
ProviderEventsManager.importCredentials()를 호출하여 흐름을 시작합니다.
가져오기 요청 구성 및 흐름 실행
가져오기가 지원하는 CredentialTypes 및 KnownExtensions를 지정합니다
.
suspend fun startCredentialImport( activityContext: Context, providerEventsManager: ProviderEventsManager ) { val importRequest = ImportCredentialsRequest( credentialTypes = setOf( CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, CredentialTypes.CREDENTIAL_TYPE_ADDRESS, CredentialTypes.CREDENTIAL_TYPE_NOTE ), knownExtensions = setOf( KnownExtensions.KNOWN_EXTENSION_SHARED ) ) try { // Launches the system Selector UI; suspends until user selects a provider and completes transfer val response = providerEventsManager.importCredentials(activityContext, importRequest) // 1. Inspect the source exporter's package info val exporterPackageName = response.callingAppInfo.packageName // 2. Parse the FIDO CXF JSON string val cxfJsonString = response.response.responseJson parseAndSaveImportedCredentials(cxfJsonString) } catch (e: ImportCredentialsException) { // Handle specific import exceptions (e.g., ImportCredentialsCancellationException) handleImportFailure(e) } } private fun parseAndSaveImportedCredentials(cxfJsonString: String) { val rootJson = JSONObject(cxfJsonString) // Parse according to FIDO Credential Exchange Format (CXF v1.0) specification: // https://fidoalliance.org/specs/cx/cxf-v1.0-ps-20250814.html } // Helper function to make it compile private fun handleImportFailure(e: ImportCredentialsException) {}
가져오기 흐름 예외
이 모듈의 모든 예외는
ImportCredentialsException, RegisterExportException 또는
ClearExportException의 서브클래스입니다.
ImportCredentialsCancellationException: 사용자가 선택기 UI를 닫거나 내보내기 활동을 취소했습니다 (Activity.RESULT_CANCELED).ImportCredentialsNoExportOptionException: 등록된 내보내기 항목이 요청된 사용자 인증 정보 유형과 일치하지 않거나 선택된 내보내기가 거부 예외를 발생시켰습니다.ImportCredentialsProviderConfigurationException: 구성 오류 (예:credentialTypes집합이ImportCredentialsRequest에 비어 있거나 권한 누락)ImportCredentialsInvalidJsonException: 내보내기가 요청 검증에 실패한 잘못된 형식의 JSON 페이로드 또는 빈 JSON 페이로드를 반환했습니다.ImportCredentialsSystemErrorException: 가져오기 중에 내부 Android 시스템 또는 바인더 전송 오류가 발생했습니다.ImportCredentialsUnknownCallerException: 프레임워크에서 통화 앱을 확인할 수 없습니다.ImportCredentialsUnknownErrorException: 가져오기 흐름 중에 분류되지 않은 오류 또는 예기치 않은 오류가 발생했습니다.
지원되는 사용자 인증 정보 유형 및 확장 프로그램
androidx.credentials.providerevents.transfer.CredentialTypes 객체는 FIDO CXF 항목 유형에 해당하는 표준화된 문자열 상수를 정의합니다.
| 상수 | 값 (cxf 유형) |
설명 |
|---|---|---|
CREDENTIAL_TYPE_BASIC_AUTH |
"basic-auth" |
사용자 이름 및 비밀번호 로그인 사용자 인증 정보 |
CREDENTIAL_TYPE_PUBLIC_KEY |
"passkey" |
FIDO2 또는 WebAuthn 패스키 공개 키 사용자 인증 정보 |
CREDENTIAL_TYPE_ADDRESS |
"address" |
양식 자동 완성의 우편 주소 또는 배송 주소 정보 |
CREDENTIAL_TYPE_API_KEY |
"api-key" |
API 액세스 키 및 토큰 |
CREDENTIAL_TYPE_CREDIT_CARD |
"credit-card" |
신용카드 및 체크카드 결제 정보 |
CREDENTIAL_TYPE_CUSTOM_FIELDS |
"custom-fields" |
맞춤 그룹 또는 사용자 정의 필드 |
CREDENTIAL_TYPE_DRIVERS_LICENSE |
"drivers-license" |
운전면허증 세부정보 |
CREDENTIAL_TYPE_FILE |
"file" |
바이너리 파일의 메타데이터 및 자리표시자 참조 |
CREDENTIAL_TYPE_GENERATED_PASSWORD |
"generated-password" |
머신 생성 보안 비밀번호 |
CREDENTIAL_TYPE_IDENTITY_DOCUMENT |
"identity-document" |
국가 신분증, SSN, TIN 또는 여권 참조 |
CREDENTIAL_TYPE_ITEM_REFERENCE |
"item-reference" |
페이로드의 다른 항목을 가리키는 논리적 링크 |
CREDENTIAL_TYPE_NOTE |
"note" |
사용자 정의 보안 메모 (UTF-8 문자열) |
CREDENTIAL_TYPE_PASSPORT |
"passport" |
여권 여행 문서 세부정보 |
CREDENTIAL_TYPE_PERSON_NAME |
"person-name" |
개인 ID 및 이름 지정 세부정보 |
CREDENTIAL_TYPE_SSH_KEY |
"ssh-key" |
SSH 공개 키 및 비공개 키 쌍 |
CREDENTIAL_TYPE_TOTP |
"totp" |
시간 기반 일회용 비밀번호 (2FA) 보안 비밀 |
CREDENTIAL_TYPE_WIFI |
"wifi" |
Wi-Fi 네트워크 SSID 및 비밀 문구 |
맞춤 WASM (WebAssembly) 일치기 (고급)
기본적으로 RegisterExportRequest.create(context, entries)를 호출하면 supportedCredentialTypes의 교차점을 기반으로 항목을 필터링하는 라이브러리 애셋의 시스템 기본 credential_transfer_matcher.wasm이 번들로 묶입니다.
사용자 인증 정보 제공업체에 복잡한 일치 로직 (예: 동적 기능 확인 또는 맞춤 필드를 기반으로 하는 조건부 필터링)이 필요한 경우 WASM 사용자 인증 정보 전송 API와 일치하는 자체 WebAssembly 모듈을 컴파일하고 원시 바이트 배열을 직접 전달할 수 있습니다.
// Loading a custom WASM matcher val customMatcherBytes = context.assets.open("my_custom_matcher.wasm").use { it.readBytes() } val customRequest = RegisterExportRequest( entries = myEntries, exportMatcher = customMatcherBytes ) providerEventsManager.registerExport(customRequest)