如要在 Android 驗證,建議使用 Credential Manager,這個 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 服務驗證功能。
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>' // ... }
以 Credential Manager 初始化取代 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 實作項目,因此不必修改。
使用密碼金鑰驗證
設定密碼金鑰建立程序後,您可以調整應用程式設定,讓使用者能使用自己的密碼金鑰登入並進行驗證。如要這麼做,您需要更新驗證碼來處理 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") { } }
從檢視畫面中,安全地移除處理意圖啟動器和活動結果呼叫的方法。
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 實作項目,因此不必修改。
其他資源
- Credential Manager 參考範例
- Credential Manager 程式碼研究室
- 透過 Credential Manager API,使用密碼金鑰在應用程式中提供順暢的驗證機制
- FIDO2 程式碼研究室