借助对 通行密钥、联合登录和第三方身份验证提供程序的支持,Credential Manager 是 Android 上推荐的身份验证 API,它提供了一个安全便捷的环境,允许用户同步和管理其凭据。对于使用本地 FIDO2 凭据的开发者,您应该通过集成 Credential Manager API 来更新您的应用以支持通行密钥身份验证。本文档介绍了如何将项目从 FIDO2 迁移到 Credential Manager。
从 FIDO2 迁移到 Credential Manager 的原因
在大多数情况下,您应该将 Android 应用的身份验证提供程序迁移到 Credential Manager。迁移到 Credential Manager 的原因包括
- 通行密钥支持:Credential Manager 支持通行密钥,这是一种新的无密码身份验证机制,比密码更安全、更易于使用。
- 多种登录方法:Credential Manager 支持多种登录方法,包括密码、通行密钥和联合登录方法。这使用户可以更轻松地验证其应用身份,无论他们偏好的身份验证方法是什么。
- 第三方凭据提供方支持:在 Android 14 及更高版本上,Credential Manager 支持多个第三方凭据提供方。这意味着您的用户可以使用其他提供方的现有凭据登录您的应用。
- 一致的用户体验:Credential Manager 为跨应用和登录机制的身份验证提供更一致的用户体验。这使用户更容易理解和使用您的应用的身份验证流程。
要开始从 FIDO2 迁移到 Credential Manager,请按照以下步骤操作。
更新依赖项
将项目 build.gradle 中的 Kotlin 插件更新到 1.8.10 或更高版本。
plugins { //… id 'org.jetbrains.kotlin.android' version '1.8.10' apply false //… }
在您项目的 build.gradle 中,更新依赖项以使用 Credential Manager 和 Play Services Authentication。
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 初始化替换为 Credential Manager 初始化。在用于通行密钥创建和登录方法的类中添加此声明
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") ) } }
安全地从您的视图中移除处理 intent 启动器和 activity 结果调用的任何方法。
由于
registerRequest()
现在返回JSONObject
,您无需创建PendingIntent
。将返回的 intent 替换为JSONObject
。更新您的 intent 启动器调用以从 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 实现类似,因此无需更改。
使用通行密钥进行身份验证
设置好通行密钥创建后,您可以设置您的应用,允许用户使用其通行密钥登录和进行身份验证。为此,您将更新您的身份验证代码以处理 Credential Manager 结果,并实现一个通过通行密钥进行身份验证的函数。
- 您向服务器发出的获取发送到
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") { } }
安全地从您的视图中移除处理 intent 启动器和 activity 结果调用的任何方法。
由于
signInRequest()
现在返回JSONObject
,您无需创建PendingIntent
。将返回的 intent 替换为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 实现类似,因此无需更改。
额外资源
- Credential Manager 示例参考
- Credential Manager Codelab
- 使用 Credential Manager API 为您的应用带来无缝的通行密钥身份验证
- FIDO2 Codelab