디지털 사용자 인증 정보 요청

Android 앱 내에서 디지털 사용자 인증 정보 확인을 사용하여 사용자의 신원 (예: 정부 발급 신분증), 사용자에 관한 속성 (예: 운전면허증, 학위 또는 연령이나 주소와 같은 속성)을 인증 및 승인하거나, 사용자 인증 정보를 발급하고 확인하여 법인의 진위 여부를 확인해야 하는 기타 시나리오를 인증 및 승인할 수 있습니다.

Verifier API는 Android 9 (API 수준 28) 이상에서 지원됩니다.

구현

Android 프로젝트에서 디지털 사용자 인증 정보를 확인하려면 다음 단계를 따르세요.

  1. 앱의 빌드 스크립트에 종속 항목을 추가하고 CredentialManager 클래스를 초기화합니다.
  2. 디지털 사용자 인증 정보 요청을 구성하고 이를 사용하여 DigitalCredentialOption를 초기화한 후 GetCredentialRequest를 빌드합니다.
  3. 구성된 요청으로 getCredential 흐름을 실행하여 성공적인 GetCredentialResponse를 수신하거나 발생할 수 있는 예외를 처리합니다. 성공적으로 검색되면 응답을 검증합니다.

종속 항목 추가 및 초기화

Gradle 빌드 스크립트에 다음 종속 항목을 추가합니다.

dependencies {
    implementation("androidx.credentials:credentials:1.6.0-beta01")
    implementation("androidx.credentials:credentials-play-services-auth:1.6.0-beta01")
}

다음으로 CredentialManager 클래스의 인스턴스를 초기화합니다.

val credentialManager = CredentialManager.create(context)

디지털 사용자 인증 정보 요청 구성

디지털 사용자 인증 정보 요청을 구성하고 이를 사용하여 DigitalCredentialOption를 초기화합니다.

// The request in the JSON format to conform with
// the JSON-ified Credential Manager - Verifier API request definition.
val requestJson = generateRequestFromServer()
val digitalCredentialOption =
    GetDigitalCredentialOption(requestJson = requestJson)

// Use the option from the previous step to build the `GetCredentialRequest`.
val getCredRequest = GetCredentialRequest(
    listOf(digitalCredentialOption)
)

다음 예는 OpenId4Vp1 요청을 보여줍니다. 이 요청의 필드에 관한 자세한 내용은 OpenID 문서를 참고하세요.

{
  "requests": [
    {
      "protocol": "openid4vp-v1-unsigned",
      "data": {
        "response_type": "vp_token",
        "response_mode": "dc_api",
        "nonce": "OD8eP8BYfr0zyhgq4QCVEGN3m7C1Ht_No9H5fG5KJFk",
        "dcql_query": {
          "credentials": [
            {
              "id": "cred1",
              "format": "mso_mdoc",
              "meta": {
                "doctype_value": "org.iso.18013.5.1.mDL"
              },
              "claims": [
                {
                  "path": [
                    "org.iso.18013.5.1",
                    "family_name"
                  ]
                },
                {
                  "path": [
                    "org.iso.18013.5.1",
                    "given_name"
                  ]
                },
                {
                  "path": [
                    "org.iso.18013.5.1",
                    "age_over_21"
                  ]
                }
              ]
            }
          ]
        }
      }
    }
  ]
}

사용자 인증 정보 가져오기

생성된 요청으로 getCredential 흐름을 실행합니다. 요청이 실패하면 성공적인 GetCredentialResponse 또는 GetCredentialException를 수신합니다.

getCredential 흐름은 Android 시스템 대화상자를 트리거하여 사용자가 사용할 수 있는 사용자 인증 정보 옵션을 표시하고 사용자의 선택을 수집합니다. 다음으로, 선택한 사용자 인증 정보 옵션이 포함된 월렛 앱은 동의를 수집하고 디지털 사용자 인증 정보 응답을 생성하는 데 필요한 작업을 실행하는 UI를 표시합니다.

coroutineScope.launch {
    try {
        val result = credentialManager.getCredential(
            context = activityContext,
            request = getCredRequest
        )
        verifyResult(result)
    } catch (e : GetCredentialException) {
        handleFailure(e)
    }
}

// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
    val credential = result.credential
    when (credential) {
        is DigitalCredential -> {
            val responseJson = credential.credentialJson
            validateResponseOnServer(responseJson)
        }
        else -> {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential ${credential.type}")
        }
    }
}

// Handle failure.
fun handleFailure(e: GetCredentialException) {
  when (e) {
        is GetCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to share the credential.
        }
        is GetCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is NoCredentialException -> {
            // No credential was available.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
    }
}
getCredential을 호출할 때 표시되는 디지털 사용자 인증 정보 선택기를 보여주는 이미지
그림 1. getCredential을 호출할 때 표시되는 디지털 사용자 인증 정보 선택기

유연한 다중 사용자 인증 정보 요청

Digital Credentials API는 OpenID4VP를 사용하여 한 번에 여러 유형의 사용자 인증 정보를 요청하는 기능도 지원합니다.

  • 동시 요청: 단일 사용자 트랜잭션에서 여러 사용자 인증 정보를 요청합니다. 예를 들어 여행 앱은 탑승권과 정부 발급 신분증을 동시에 요청할 수 있으므로 사용자는 한 번의 탭으로 두 가지를 모두 검토하고 동의할 수 있습니다.
  • 유연한 처리: 여러 사용자 인증 정보 유형 또는 발급기관 중 하나를 허용합니다. 예를 들어 연령 인증 흐름은 운전면허증 또는 여권을 허용할 수 있으므로 사용자는 소지품에 저장된 사용자 인증 정보를 사용하여 요청을 처리할 수 있습니다.

다음은 단일 요청에서 여러 사용자 인증 정보를 요청하는 예입니다.

{
  "credentials": [
    {
      "id": "gov_id1",
      "format": "mso_mdoc",
      "meta": {
        "doctype_value": "org.iso.18013.5.1.mDL"
      },
      "claims": [
        {
          "path": [
            "org.iso.18013.5.1",
            "age_over_21"
          ]
        }
      ]
    },
    {
      "id": "boarding_pass1",
      "format": "dc+sd-jwt",
      "meta": {
        "vct_values": [
          "https://credentials.example.airline/boarding-pass"
        ]
      },
      "claims": [
        {
          "path": [
            "flight_number"
          ]
        },
        {
          "path": [
            "seat"
          ]
        }
      ]
    }
  ]
}
여러 유형의 사용자 인증 정보를 호출할 때 표시되는 디지털 사용자 인증 정보 선택기를 보여주는 이미지
그림 1. 여러 유형의 사용자 인증 정보를 호출할 때 표시되는 디지털 사용자 인증 정보 선택기

응답 처리

사용자 인증 정보 요청을 한 후 CredentialManager에서 반환된 GetCredentialResponse를 처리합니다.

// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
    val credential = result.credential
    when (credential) {
        is DigitalCredential -> {
            val responseJson = credential.credentialJson
            validateResponseOnServer(responseJson)
        }
        else -> {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential ${credential.type}")
        }
    }
}

예외 처리

앱은 기기에 사용자 인증 정보가 없거나 사용자가 작업을 취소하는 경우를 비롯하여 발생할 수 있는 오류를 처리해야 합니다.

// Handle failure.
fun handleFailure(e: GetCredentialException) {
    when (e) {
        is GetCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to share the credential.
        }
        is GetCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is NoCredentialException -> {
            // No credential was available.
        }
        is CreateCredentialUnknownException -> {
            // An unknown, usually unexpected, error has occurred. Check the
            // message error for any additional debugging information.
        }
        is CreateCredentialCustomException -> {
            // You have encountered a custom error thrown by the wallet.
            // If you made the API call with a request object that's a
            // subclass of CreateCustomCredentialRequest using a 3rd-party SDK,
            // then you should check for any custom exception type constants
            // within that SDK to match with e.type. Otherwise, drop or log the
            // exception.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
    }
}