보유자 앱을 인증 관리자와 통합

Credential Manager Holder API를 사용하면 Android 소유자('지갑'이라고도 함) 앱이 디지털 사용자 인증 정보를 관리하고 인증 기관에 표시할 수 있습니다.

인증 관리자의 디지털 사용자 인증 정보 UI를 보여주는 이미지
그림 1. 디지털 사용자 인증 정보 선택기 UI

핵심 개념

Holder API를 사용하기 전에 다음 개념을 숙지해야 합니다.

사용자 인증 정보 형식

사용자 인증 정보는 다양한 사용자 인증 정보 형식으로 보유자 앱에 저장될 수 있습니다. 이러한 형식은 사용자 인증 정보가 표시되는 방식에 관한 사양이며, 각 형식에는 사용자 인증 정보에 관한 다음 정보가 포함됩니다.

  • 유형: 대학 학위 또는 모바일 운전면허증과 같은 카테고리입니다.
  • 속성: 이름, 성과 같은 속성입니다.
  • 인코딩: 사용자 인증 정보가 구조화되는 방식입니다(예: SD-JWT 또는 mdoc).
  • 유효성: 사용자 인증 정보의 신뢰성을 암호화 방식으로 확인하는 방법입니다.

각 사용자 인증 정보 형식은 인코딩과 유효성 검사를 약간 다르게 실행하지만 기능적으로는 동일합니다.

레지스트리는 두 가지 형식을 지원합니다.

인증 기관은 사용자 인증 정보 관리자를 사용할 때 SD-JWT 및 mdoc에 대한 OpenID4VP 요청을 할 수 있습니다. 선택은 사용 사례와 업계 선택에 따라 달라집니다.

사용자 인증 정보 메타데이터 등록

사용자 인증 정보 관리자는 보유자의 사용자 인증 정보를 직접 저장하지 않고 사용자 인증 정보의 메타데이터를 저장합니다. 보유자 앱은 먼저 RegistryManager를 사용하여 Credential Manager에 사용자 인증 정보 메타데이터를 등록해야 합니다. 이 등록 프로세스는 다음과 같은 두 가지 주요 용도로 사용되는 레지스트리 레코드를 만듭니다.

  • 매칭: 등록된 사용자 인증 정보 메타데이터는 향후 인증 기관 요청과 일치하는 데 사용됩니다.
  • 표시: 맞춤설정된 UI 요소가 사용자에게 사용자 인증 정보 선택기 인터페이스에 표시됩니다.

OpenId4VpRegistry 클래스는 mdoc 및 SD-JWT 사용자 인증 정보 형식을 모두 지원하므로 이 클래스를 사용하여 디지털 사용자 인증 정보를 등록합니다. 인증 기관은 이러한 인증 정보를 요청하기 위해 OpenID4VP 요청을 전송합니다.

앱의 사용자 인증 정보 등록

인증 관리자 홀더 API를 사용하려면 앱 모듈의 빌드 스크립트에 다음 종속 항목을 추가하세요.

Groovy

dependencies {
    // Use to implement credentials registrys

    implementation "androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha04"
    implementation "androidx.credentials.registry:registry-digitalcredentials-preview:1.0.0-alpha04"
    implementation "androidx.credentials.registry:registry-provider:1.0.0-alpha04"
    implementation "androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha04"

}

Kotlin

dependencies {
    // Use to implement credentials registrys

    implementation("androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha04")
    implementation("androidx.credentials.registry:registry-digitalcredentials-preview:1.0.0-alpha04")
    implementation("androidx.credentials.registry:registry-provider:1.0.0-alpha04")
    implementation("androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha04")

}

RegistryManager 만들기

RegistryManager 인스턴스를 만들고 OpenId4VpRegistry 요청을 등록합니다.

// Create the registry manager
val registryManager = RegistryManager.create(context)

// The guide covers how to build this out later
val registryRequest = OpenId4VpRegistry(credentialEntries, id)

try {
    registryManager.registerCredentials(registryRequest)
} catch (e: Exception) {
    // Handle exceptions
}

OpenId4VpRegistry 요청 빌드

앞서 언급한 것처럼 검증 기관의 OpenID4VP 요청을 처리하려면 OpenId4VpRegistry를 등록해야 합니다. 지갑 사용자 인증 정보 (예: sdJwtsFromStorage)가 로드된 로컬 데이터 유형이 있다고 가정합니다. 이제 형식을 기반으로 Jetpack DigitalCredentialEntry에 상응하는 항목으로 변환합니다. SD-JWT의 경우 SdJwtEntry, mdoc의 경우 MdocEntry입니다.

레지스트리에 Sd-JWT 추가

각 로컬 SD-JWT 사용자 인증 정보를 등록처의 SdJwtEntry에 매핑합니다.

fun mapToSdJwtEntries(sdJwtsFromStorage: List<StoredSdJwtEntry>): List<SdJwtEntry> {
    val list = mutableListOf<SdJwtEntry>()

    for (sdJwt in sdJwtsFromStorage) {
        list.add(
            SdJwtEntry(
                verifiableCredentialType = sdJwt.getVCT(),
                claims = sdJwt.getClaimsList(),
                entryDisplayPropertySet = sdJwt.toDisplayProperties(),
                id = sdJwt.getId() // Make sure this cannot be readily guessed
            )
        )
    }
    return list
}

레지스트리에 mdocs 추가

로컬 mdoc 사용자 인증 정보를 Jetpack 유형 MdocEntry에 매핑합니다.

fun mapToMdocEntries(mdocsFromStorage: List<StoredMdocEntry>): List<MdocEntry> {
    val list = mutableListOf<MdocEntry>()

    for (mdoc in mdocsFromStorage) {
        list.add(
            MdocEntry(
                docType = mdoc.retrieveDocType(),
                fields = mdoc.getFields(),
                entryDisplayPropertySet = mdoc.toDisplayProperties(),
                id = mdoc.getId() // Make sure this cannot be readily guessed
            )
        )
    }
    return list
}

코드에 관한 핵심 사항

  • id 필드를 구성하는 한 가지 방법은 암호화된 사용자 인증 정보 식별자를 등록하는 것입니다. 이렇게 하면 값의 복호화가 사용자만 가능합니다.
  • 두 형식의 UI 표시 필드는 현지화되어야 합니다.

사용자 인증 정보 등록

변환된 항목을 결합하고 RegistryManager로 요청을 등록합니다.

val credentialEntries = mapToSdJwtEntries(sdJwtsFromStorage) + mapToMdocEntries(mdocsFromStorage)

val openidRegistryRequest = OpenId4VpRegistry(
    credentialEntries = credentialEntries,
    id = "my-wallet-openid-registry-v1" // A stable, unique ID to identify your registry record.
)

이제 CredentialManager에 사용자 인증 정보를 등록할 준비가 되었습니다.

try {
    val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
    // Handle failure
}

이제 인증 관리자에 사용자 인증 정보를 등록했습니다.

앱 메타데이터 관리

보유자 앱이 CredentialManager에 등록하는 메타데이터에는 다음 속성이 있습니다.

  • 지속성: 정보는 로컬에 저장되어 재부팅 시에도 유지됩니다.
  • 사일로화된 스토리지: 각 앱의 레지스트리 기록은 별도로 저장되므로 한 앱이 다른 앱의 레지스트리 기록을 변경할 수 없습니다.
  • 키가 지정된 업데이트: 각 앱의 레지스트리 레코드는 id로 키가 지정되어 레코드를 다시 식별하거나 업데이트하거나 삭제할 수 있습니다.
  • 메타데이터 업데이트: 앱이 변경되거나 처음 로드될 때마다 지속된 메타데이터를 업데이트하는 것이 좋습니다. 동일한 id에서 레지스트리가 여러 번 호출되면 최신 호출이 이전 레코드를 모두 덮어씁니다. 업데이트하려면 이전 레코드를 먼저 삭제하지 않고 다시 등록하세요.

선택사항: 매처 만들기

매처는 Credential Manager가 샌드박스에서 실행하여 등록된 사용자 인증 정보를 수신된 인증 기관 요청에 대해 필터링하는 Wasm 바이너리입니다.

  • 기본 매처: OpenId4VpRegistry 클래스를 인스턴스화하면 기본 OpenId4VP 매처가 자동으로 포함됩니다 (OpenId4VpDefaults.DEFAULT_MATCHER). 모든 표준 OpenID4VP 사용 사례에서 라이브러리는 매칭을 처리합니다.
  • 맞춤 매처: 자체 매칭 로직이 필요한 비표준 프로토콜을 지원하는 경우에만 맞춤 매처를 구현합니다.

선택한 사용자 인증 정보 처리

사용자가 사용자 인증 정보를 선택하면 보유자 앱이 요청을 처리해야 합니다. androidx.credentials.registry.provider.action.GET_CREDENTIAL 인텐트 필터를 수신하는 활동을 정의해야 합니다. 샘플 지갑에서 이 절차를 보여줍니다.

인텐트는 검증 도구 요청과 호출 출처를 사용하여 활동을 시작합니다. PendingIntentHandler.retrieveProviderGetCredentialRequest 함수로 추출합니다. 그러면 인증 기관 요청과 관련된 모든 정보가 포함된 ProviderGetCredentialRequest이 반환됩니다. 세 가지 주요 구성요소가 있습니다.

  • 호출 앱: 요청을 한 앱으로, getCallingAppInfo로 가져올 수 있습니다.
  • 선택한 인증 정보: 사용자가 선택한 후보에 관한 정보로, selectedCredentialSet extension method를 통해 가져옵니다. 이는 등록한 인증 정보 ID와 일치합니다.
  • 구체적인 요청: 검증 도구에서 요청한 구체적인 요청으로, getCredentialOptions 메서드에서 가져옵니다. 디지털 사용자 인증 정보 요청 흐름의 경우 이 목록에 단일 GetDigitalCredentialOption이 표시됩니다.

가장 일반적으로 인증 기관은 디지털 사용자 인증 정보 프레젠테이션 요청을 하며, 다음 샘플 코드로 처리할 수 있습니다.

request.credentialOptions.forEach { option ->
    if (option is GetDigitalCredentialOption) {
        Log.i(TAG, "Got DC request: ${option.requestJson}")
        processRequest(option.requestJson)
    }
}

이 예는 샘플 지갑에서 확인할 수 있습니다.

인증자 ID 확인

  1. 인텐트에서 ProviderGetCredentialRequest 추출:
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
  1. 권한이 있는 출처 확인: 권한이 있는 앱 (예: 웹브라우저)은 출처 매개변수를 설정하여 다른 검증자를 대신하여 호출할 수 있습니다. 이 출처를 가져오려면 권한이 있고 신뢰할 수 있는 호출자 목록(JSON 형식의 허용 목록)을 CallingAppInfogetOrigin() API에 전달해야 합니다.
val origin = request?.callingAppInfo?.getOrigin(
    privilegedAppsJson // Your allow list JSON
)

출처가 비어 있지 않은 경우: packageNamesigningInfo에서 가져온 인증서 지문이 getOrigin() API에 전달된 허용 목록에 있는 앱의 것과 일치하는 경우 출처가 반환됩니다. 원본 값을 가져온 후 제공업체 앱은 이를 권한이 있는 호출로 간주하고 호출 앱의 서명을 사용하여 원본을 계산하는 대신 OpenID4VP 응답에 이 원본을 설정해야 합니다.

Google 비밀번호 관리자는 getOrigin() 호출에 공개적으로 사용 가능한 허용 목록을 사용합니다. 사용자 인증 정보 제공업체는 이 목록을 사용하거나 API에 설명된 JSON 형식으로 자체 목록을 제공할 수 있습니다. 사용할 목록을 선택하는 것은 제공업체가 결정합니다. 서드 파티 사용자 인증 정보 제공업체를 통해 권한이 있는 액세스를 얻으려면 서드 파티에서 제공한 문서를 참고하세요.

origin이 비어 있으면 확인자 요청이 Android 앱에서 온 것입니다. OpenID4VP 응답에 넣을 앱 출처는 android:apk-key-hash:<encoded SHA 256 fingerprint>로 계산해야 합니다.

val appSigningInfo = request?.callingAppInfo?.signingInfoCompat?.signingCertificateHistory[0]?.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val certHash = Base64.encodeToString(md.digest(appSigningInfo), Base64.NO_WRAP or Base64.NO_PADDING)
return "android:apk-key-hash:$certHash"

홀더 UI 렌더링

사용자 인증 정보가 선택되면 보유자 앱이 호출되어 사용자를 앱의 UI로 안내합니다. 이 워크플로를 처리하는 표준 방법에는 두 가지가 있습니다.

  • 사용자 인증 정보를 해제하는 데 추가 사용자 인증이 필요한 경우 BiometricPrompt API를 사용하세요. 샘플에서 확인할 수 있습니다.
  • 그렇지 않으면 많은 지갑이 호출 앱에 데이터를 즉시 다시 전달하는 빈 활동을 렌더링하여 자동 반환을 선택합니다. 이렇게 하면 사용자 클릭이 최소화되고 더 원활한 환경이 제공됩니다.

사용자 인증 정보 응답 반환

보유자 앱이 결과를 다시 보낼 준비가 되면 사용자 인증 정보 응답으로 활동을 완료합니다.

PendingIntentHandler.setGetCredentialResponse(
    resultData,
    GetCredentialResponse(DigitalCredential(response.responseJson))
)
setResult(RESULT_OK, resultData)
finish()

예외가 있는 경우 다음과 같이 사용자 인증 정보 예외를 보낼 수 있습니다.

PendingIntentHandler.setGetCredentialException(
    resultData,
    GetCredentialUnknownException() // Configure the proper exception
)
setResult(RESULT_OK, resultData)
finish()

컨텍스트에서 사용자 인증 정보 응답을 반환하는 전체 예시는 샘플 앱을 참고하세요.