2025년 4분기 수정: Android 앱에서 Credential Manager API를 사용하여 인증 과정을 간소화하는 방법 알아보기

1. 시작하기 전에

기존 인증 솔루션은 여러 보안 및 사용성 문제가 있습니다.

비밀번호는 널리 사용되지만 다음과 같은 문제가 있습니다.

  • 쉽게 잊어버림
  • 안전한 비밀번호를 만들려면 사용자에게 지식이 요구됨
  • 공격자가 피싱하고 수집하고 재생하기 쉬움

Android에서는 비밀번호 없는 인증의 차세대 업계 표준인 패스키를 지원하여 로그인 환경을 간소화하고 보안 위험을 해결하는 Credential Manager API를 만들기 위해 노력했습니다.

인증 관리자는 패스키 지원을 통합하고 이를 비밀번호, Google 계정으로 로그인 등 기존 인증 방법과 결합합니다.

사용자는 패스키를 만들어 Google 비밀번호 관리자에 저장할 수 있으며 Google 비밀번호 관리자는 이러한 패스키를 사용자가 로그인하는 다양한 Android 기기에서 동기화합니다. 패스키를 생성하여 사용자 계정과 연결해야 하며, 공개 키를 서버에 저장해야 사용자가 로그인할 수 있습니다.

이 Codelab에서는 Credential Manager API를 사용하여 패스키와 비밀번호로 가입하고 이를 향후 인증용으로 사용하는 방법을 알아봅니다. 다음 두 가지 흐름이 있습니다.

  • 가입: 패스키와 비밀번호 사용
  • 로그인: 패스키와 저장된 비밀번호 사용

기본 요건

  • Android 스튜디오에서 앱을 실행하는 방법에 관한 기본 이해
  • Android 앱의 인증 흐름에 관한 기본 이해
  • 패스키에 관한 기본 이해

학습할 내용

  • 패스키 생성 방법
  • 비밀번호 관리자에 비밀번호를 저장하는 방법
  • 패스키나 저장된 비밀번호로 사용자를 인증하는 방법

필요한 항목

다음 기기 조합 중 하나:

  • Android 9 이상(패스키용) 및 Android 4.4 이상(Credential Manager API를 통한 비밀번호 인증용)을 실행하는 Android 기기
  • 생체 인식 센서가 적용된 기기면 더 좋음
  • 화면 잠금 (생체 인식 또는 기타)을 등록해야 합니다.
  • Kotlin 플러그인 버전: 1.8.10

2. 설정

이 샘플 앱에는 인증 관리자가 연결을 검증하고 추가로 진행하기 위해 웹사이트에 연결되는 디지털 애셋이 필요하므로 모의 응답에서 사용되는 rp ID는 모의 서드 파티 서버에서 가져온 것입니다. 자체 모의 응답을 시도하려면 앱 도메인을 추가하고 여기 언급된 디지털 애셋 연결을 완료해야 합니다.

프로젝트에서 언급된 동일한 debug.keystore를 사용하여 디버그 및 출시 변형을 빌드하고 모의 서버에서 패키지 이름과 sha의 디지털 애셋 연결을 확인합니다. build.gradle의 샘플 앱에는 이미 이 작업이 완료되어 있습니다.

  1. credman_codelab 브랜치(https://github.com/android/identity-samples/tree/credman_codelab)에서 노트북에 이 저장소를 클론합니다.
git clone -b credman_codelab https://github.com/android/identity-samples.git
  1. CredentialManager 모듈로 이동하여 Android 스튜디오에서 프로젝트를 엽니다.

앱의 초기 상태 확인

앱의 초기 상태가 어떻게 작동하는지 확인하려면 다음 단계를 따르세요.

  1. 앱을 실행합니다.
  2. 가입 및 로그인 버튼이 있는 기본 화면이 표시됩니다. 이 버튼은 아직 아무것도 하지 않지만, 다음 섹션에서 기능을 사용 설정합니다.

7a6fe80f4cf877a8.jpeg

3. 패스키를 사용한 가입 기능 추가

Credential Manager API를 사용하는 Android 앱에서 새 계정에 가입할 때 사용자는 계정의 패스키를 만들 수 있습니다. 이 패스키는 사용자가 선택한 사용자 인증 정보 제공업체에 안전하게 저장되어 향후 로그인에 사용되므로 사용자가 매번 비밀번호를 입력하지 않아도 됩니다.

이제 패스키를 만들고 생체 인식/화면 잠금을 사용하여 사용자 인증 정보를 등록합니다.

패스키로 가입

CredentialManager/app/src/main/java/com/google/credentialmanager/sample/SignUpScreen.kt 내부의 코드는 'username' 텍스트 필드와 패스키로 가입하는 버튼을 정의합니다.

1f4c50daa2551f1.jpeg

뷰 모델에서 사용할 createCredential() 람다 정의

인증 관리자 객체에는 Activity가 전달되어야 하며, 이는 화면과 연결됩니다. 하지만 일반적으로 View Models에서 Credential manager 작업이 트리거되며 View Models 내에서 Activities를 참조하는 것은 권장되지 않습니다. 따라서 별도의 파일 CredentialManagerUtil.kt에 사용자 인증 정보 관리자 함수를 정의하고 적절한 화면에서 이를 참조한 다음, 람다 함수를 통해 콜백으로 뷰 모델에 전달합니다.

CredentialManagerUtil.ktcreateCredential() 함수에서 TODO 주석을 찾아 CredentialManager.create() 함수를 호출합니다.

CredentialManagerUtil.kt

suspend fun createCredential(
    activity: Activity,
    request: CreateCredentialRequest
): CreateCredentialResponse {
    TODO("Create a CredentialManager object and call createCredential() with a CreateCredentialRequest")
    val credentialManager = CredentialManager.create(activity)
    return credentialManager.createCredential(activity, request)
}

챌린지와 기타 json 응답을 createPasskey() 호출에 전달

패스키를 만들기 전에 createCredential 호출 중 Credential Manager API에 전달할 필요한 정보를 서버에 요청해야 합니다.

이 Codelab에서는 필요한 매개변수를 반환하는 모의 응답이 프로젝트의 애셋(RegFromServer.txt)에 이미 있습니다.

  • 앱에서 SignUpViewModel.kt로 이동하여 signUpWithPasskeys 메서드를 찾습니다. 여기서 패스키 생성 및 사용자 가입을 위한 로직을 작성합니다. 동일한 클래스에서 이 메서드를 찾을 수 있습니다.
  • TODO 주석 블록을 create a CreatePublicKeyCredentialRequest()로 찾아 다음 코드로 바꿉니다.

SignUpViewModel.kt

TODO("Create a CreatePublicKeyCredentialRequest() with necessary registration json from server")
    val request = CreatePublicKeyCredentialRequest(
        jsonProvider.fetchRegistrationJson()
            .replace("<userId>", getEncodedUserId())
            .replace("<userName>", _username.value)
            .replace("<userDisplayName>", _username.value)
            .replace("<challenge>", getEncodedChallenge())
    )

jsonProvider.fetchRegistrationJsonFromServer() 메서드는 애셋에서 에뮬레이트된 서버 PublicKeyCredentialCreationOptions JSON 응답을 읽고 패스키를 생성하는 동안 전달할 등록 JSON을 반환합니다. 자리표시자 값 중 일부를 앱의 사용자 입력과 모의 필드로 바꿉니다.

  • 이 JSON은 불완전하며 4개의 필드를 교체해야 합니다.
  • UserId는 고유해야 하므로 사용자가 여러 패스키를 만들 수 있습니다(필요한 경우). <userId>를 생성된 userId 값으로 바꿉니다.
  • <challenge>도 고유해야 하므로 임의의 고유한 챌린지를 생성하게 됩니다. 이 메서드는 이미 코드에 있습니다.

실제 서버 PublicKeyCredentialCreationOptions 응답은 더 많은 옵션을 반환할 수 있습니다. 이러한 필드의 예는 다음과 같습니다.

{
  "challenge": String,
  "rp": {
    "name": String,
    "id": String
  },
  "user": {
    "id": String,
    "name": String,
    "displayName": String
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [],
  "authenticatorSelection": {
    "authenticatorAttachment"