패스키, 제휴 로그인, 서드 파티 인증 제공업체를 지원하는 인증 관리자는 Android 인증을 위해 권장되는 API로, 사용자가 사용자 인증 정보를 동기화하고 관리할 수 있는 안전하고 편리한 환경을 제공합니다. 로컬 FIDO2 사용자 인증 정보를 사용하는 개발자의 경우 Credential Manager API와 통합하여 패스키 인증을 지원하도록 앱을 업데이트해야 합니다. 이 문서에서는 FIDO2에서 인증 관리자로 프로젝트를 이전하는 방법을 설명합니다.
FIDO2에서 인증 관리자로 이전해야 하는 이유
대부분의 경우 Android 앱의 인증 제공자를 인증 관리자로 이전해야 합니다. 인증 관리자로 이전하는 이유는 다음과 같습니다.
- 패스키 지원: 인증 관리자는 비밀번호보다 더 안전하고 사용하기 쉬운 비밀번호가 없는 새로운 인증 메커니즘인 패스키를 지원합니다.
- 멀티 로그인 방법: 인증 관리자는 비밀번호, 패스키, 제휴 로그인 방법 등의 멀티 로그인 방법을 지원합니다. 이렇게 하면 사용자가 선호하는 인증 방법과 관계없이 더 쉽게 앱에 인증할 수 있습니다.
- 서드 파티 사용자 인증 정보 제공업체 지원: Android 14 이상에서 인증 관리자는 여러 서드 파티 사용자 인증 정보 제공업체를 지원합니다. 즉, 사용자가 다른 제공업체의 기존 사용자 인증 정보를 사용하여 앱에 로그인할 수 있습니다.
- 일관된 사용자 환경: 인증 관리자는 여러 앱 및 로그인 메커니즘에서 인증에 더 일관된 사용자 환경을 제공합니다. 이렇게 하면 사용자가 앱의 인증 흐름을 더 쉽게 이해하고 사용할 수 있습니다.
FIDO2에서 인증 관리자로 이전을 시작하려면 아래 단계를 따르세요.
종속 항목 업데이트
프로젝트의 build.gradle에서 Kotlin 플러그인을 버전 1.8.10 이상으로 업데이트합니다.
plugins { //… id 'org.jetbrains.kotlin.android' version '1.8.10' apply false //… }
프로젝트의 build.gradle에서 인증 관리자 및 Play 서비스 인증을 사용하도록 종속 항목을 업데이트합니다.
dependencies { // ... // Credential Manager: implementation 'androidx.credentials:credentials:<latest-version>' // Play Services Authentication: // Optional - needed for credentials support from play services, for devices running // Android 13 and below: implementation 'androidx.credentials:credentials-play-services-auth:<latest-version>' // ... }
FIDO 초기화를 인증 관리자 초기화로 대체합니다. 패스키 생성 및 로그인 방법에 사용하는 클래스에서 이 선언을 추가합니다.
val credMan = CredentialManager.create(context)
패스키 만들기
사용자가 패스키로 로그인할 수 있도록 하려면 먼저 새 패스키를 만들고 이를 사용자 계정과 연결한 후 패스키의 공개 키를 서버에 저장해야 합니다. 등록 함수 호출을 업데이트하여 이 기능으로 앱을 설정합니다.
패스키 생성 중에
createCredential()
메서드로 전송되는 필수 매개변수를 가져오려면 WebAuthn 사양에 설명된 대로name("residentKey").value("required")
를registerRequest()
서버 호출에 추가합니다.suspend fun registerRequest(sessionId: String ... { // ... .method("POST", jsonRequestBody { name("attestation").value("none") name("authenticatorSelection").objectValue { name("residentKey").value("required") } }).build() // ... }
registerRequest()
및 모든 하위 함수의return
유형을JSONObject
로 설정합니다.suspend fun registerRequest(sessionId: String): ApiResult<JSONObject> { val call = client.newCall( Request.Builder() .url("$BASE_URL/<your api url>") .addHeader("Cookie", formatCookie(sessionId)) .method("POST", jsonRequestBody { name("attestation").value("none") name("authenticatorSelection").objectValue { name("authenticatorAttachment").value("platform") name("userVerification").value("required") name("residentKey").value("required") } }).build() ) val response = call.await() return response.result("Error calling the api") { parsePublicKeyCredentialCreationOptions( body ?: throw ApiException("Empty response from the api call") ) } }
인텐트 런처 및 활동 결과 호출을 처리하는 메서드를 뷰에서 안전하게 삭제합니다.
이제
registerRequest()
가JSONObject
를 반환하므로PendingIntent
를 만들지 않아도 됩니다. 반환된 인텐트를JSONObject
로 바꿉니다. Credential Manager API에서createCredential()
을 호출하도록 인텐트 런처 호출을 업데이트합니다.createCredential()
API 메서드를 호출합니다.suspend fun createPasskey( activity: Activity, requestResult: JSONObject ): CreatePublicKeyCredentialResponse? { val request = CreatePublicKeyCredentialRequest(requestResult.toString()) var response: CreatePublicKeyCredentialResponse? = null try { response = credMan.createCredential( request as CreateCredentialRequest, activity ) as CreatePublicKeyCredentialResponse } catch (e: CreateCredentialException) { showErrorAlert(activity, e) return null } return response }
호출이 성공하면 응답을 서버로 다시 전송합니다. 이 호출의 요청 및 응답은 FIDO2 구현과 유사하므로 변경할 필요가 없습니다.
패스키로 인증
패스키 생성을 설정한 후에는 사용자가 패스키를 사용하여 로그인하고 인증할 수 있도록 앱을 설정할 수 있습니다. 이렇게 하려면 인증 관리자 결과를 처리하도록 인증 코드를 업데이트하고 패스키를 통해 인증하는 함수를 구현합니다.
getCredential()
요청으로 전송하는 데 필요한 정보를 가져오기 위한 서버에 대한 로그인 요청 호출은 FIDO2 구현과 동일합니다. 변경하지 않아도 됩니다.등록 요청 호출과 마찬가지로 반환된 응답은 JSONObject 형식입니다.
/** * @param sessionId The session ID to be used for the sign-in. * @param credentialId The credential ID of this device. * @return a JSON object. */ suspend fun signinRequest(): ApiResult<JSONObject> { val call = client.newCall(Builder().url(buildString { append("$BASE_URL/signinRequest") }).method("POST", jsonRequestBody {}) .build() ) val response = call.await() return response.result("Error calling /signinRequest") { parsePublicKeyCredentialRequestOptions( body ?: throw ApiException("Empty response from /signinRequest") ) } } /** * @param sessionId The session ID to be used for the sign-in. * @param response The JSONObject for signInResponse. * @param credentialId id/rawId. * @return A list of all the credentials registered on the server, * including the newly-registered one. */ suspend fun signinResponse( sessionId: String, response: JSONObject, credentialId: String ): ApiResult<Unit> { val call = client.newCall( Builder().url("$BASE_URL/signinResponse") .addHeader("Cookie",formatCookie(sessionId)) .method("POST", jsonRequestBody { name("id").value(credentialId) name("type").value(PUBLIC_KEY.toString()) name("rawId").value(credentialId) name("response").objectValue { name("clientDataJSON").value( response.getString("clientDataJSON") ) name("authenticatorData").value( response.getString("authenticatorData") ) name("signature").value( response.getString("signature") ) name("userHandle").value( response.getString("userHandle") ) } }).build() ) val apiResponse = call.await() return apiResponse.result("Error calling /signingResponse") { } }
인텐트 런처 및 활동 결과 호출을 처리하는 메서드를 뷰에서 안전하게 삭제합니다.
이제
signInRequest()
가JSONObject
를 반환하므로PendingIntent
를 만들지 않아도 됩니다. 반환된 인텐트를JSONObject
로 바꾸고 API 메서드에서getCredential()
을 호출합니다.suspend fun getPasskey( activity: Activity, creationResult: JSONObject ): GetCredentialResponse? { Toast.makeText( activity, "Fetching previously stored credentials", Toast.LENGTH_SHORT) .show() var result: GetCredentialResponse? = null try { val request= GetCredentialRequest( listOf( GetPublicKeyCredentialOption( creationResult.toString(), null ), GetPasswordOption() ) ) result = credMan.getCredential(activity, request) if (result.credential is PublicKeyCredential) { val publicKeycredential = result.credential as PublicKeyCredential Log.i("TAG", "Passkey ${publicKeycredential.authenticationResponseJson}") return result } } catch (e: Exception) { showErrorAlert(activity, e) } return result }
호출이 성공하면 서버로 응답을 다시 전송하여 사용자를 검증하고 인증합니다. 이 API 호출의 요청 및 응답 매개변수는 FIDO2 구현과 유사하므로 변경하지 않아도 됩니다.