Credential Manager merujuk pada sekumpulan API yang diperkenalkan di Android 14 dan mendukung beberapa metode login seperti nama pengguna dan sandi, kunci sandi, serta solusi login federasi (seperti Login dengan Google). Saat Credential Manager API dipanggil, sistem Android akan menggabungkan kredensial dari semua penyedia kredensial yang diinstal di perangkat. Dokumen ini menjelaskan kumpulan API yang menyediakan endpoint integrasi untuk penyedia kredensial ini.
Penyiapan
Sebelum menerapkan fungsi di penyedia kredensial Anda, selesaikan langkah-langkah penyiapan yang ditampilkan di bagian berikut.
Mendeklarasikan dependensi
Dalam file build.gradle
modul Anda, deklarasikan dependensi menggunakan versi
terbaru library Credential Manager:
implementation "androidx.credentials:credentials:1.2.0-{latest}"
Mendeklarasikan elemen layanan dalam file manifes
Di file manifes aplikasi Anda AndroidManifest.xml
, sertakan deklarasi <service>
untuk class layanan yang memperluas
class CredentialProviderService
dari library androidx.credentials,
seperti ditunjukkan dalam contoh di bawah.
<service android:name=".MyCredentialProviderService"
android:enabled="true"
android:exported="true"
android:label="My Credential Provider"
android:icon="<any drawable icon>"
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE">
<intent-filter>
<action android:name="android.service.credentials.CredentialProviderService"/>
</intent-filter>
<meta-data
android:name="android.credentials.provider"
android:resource="@xml/provider"/>
</service>
Izin dan filter intent yang ditampilkan di atas merupakan bagian integral agar alur Credential Manager dapat berfungsi seperti yang diharapkan. Izin diperlukan sehingga hanya sistem Android yang dapat mengikat ke layanan ini. Filter intent digunakan untuk visibilitas layanan ini sebagai penyedia kredensial yang akan digunakan oleh Credential Manager.
Mendeklarasikan jenis kredensial yang didukung
Di direktori res/xml
, buat file baru bernama provider.xml
. Dalam
file ini, deklarasikan jenis kredensial yang didukung layanan Anda melalui konstanta
yang ditetapkan untuk setiap jenis kredensial di library. Pada contoh
berikut, layanan mendukung sandi tradisional serta kunci sandi,
yang konstantanya ditentukan sebagai
TYPE_PASSWORD_CREDENTIAL
dan TYPE_PUBLIC_KEY_CREDENTIAL
:
<?xml version="1.0" encoding="utf-8"?>
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
<capabilities>
<capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>
Pada level API sebelumnya, penyedia kredensial terintegrasi dengan API seperti isi otomatis untuk sandi dan data lainnya. Penyedia ini dapat menggunakan infrastruktur internal yang sama untuk menyimpan jenis kredensial yang ada, sekaligus memperluasnya untuk mendukung yang lain, termasuk kunci sandi.
Pendekatan dua fase untuk interaksi penyedia
Credential Manager berinteraksi dengan penyedia kredensial dalam dua fase:
- Fase pertama adalah fase memulai/kueri yang memungkinkan sistem mengikat
ke layanan penyedia kredensial dan memanggil metode
onBeginGetCredentialRequest()
,onBeginCreateCredentialRequest()
, atauonClearCredentialStateRequest()
dengan permintaanBegin…
. Penyedia harus memproses permintaan ini dan merespons dengan responsBegin…
, yang akan mengisinya dengan entri yang mewakili opsi visual yang akan ditampilkan di pemilih akun. Setiap entri harus memiliki kumpulanPendingIntent
. - Setelah pengguna memilih entri, fase pemilihan akan dimulai dan
PendingIntent
yang terkait dengan entri tersebut akan diaktifkan sehingga aktivitas penyedia yang sesuai akan muncul. Setelah pengguna selesai berinteraksi dengan aktivitas ini, penyedia kredensial harus menetapkan respons terhadap hasil aktivitas sebelum mengakhirinya. Respons ini kemudian dikirim ke aplikasi klien yang memanggil Credential Manager.
Menangani pembuatan kunci sandi
Menangani kueri untuk pembuatan kunci sandi
Saat aplikasi klien ingin membuat kunci sandi dan menyimpannya dengan
penyedia kredensial, mereka akan memanggil createCredential
API. Untuk menangani
permintaan ini di layanan penyedia kredensial Anda agar kunci sandi benar-benar
disimpan dalam penyimpanan Anda, selesaikan langkah-langkah yang ditunjukkan di bagian berikut.
- Ganti metode
onBeginCreateCredentialRequest()
di layanan Anda yang diperluas dariCredentialProviderService
. - Tangani
BeginCreateCredentialRequest
dengan membuatBeginCreateCredentialResponse
yang sesuai dan meneruskannya melalui callback. - Saat membuat
BeginCreateCredentialResponse
, tambahkanCreateEntries
yang diperlukan. SetiapCreateEntry
harus sesuai dengan akun tempat kredensial dapat disimpan, dan harus memilikiPendingIntent
yang ditetapkan bersama metadata lain yang diperlukan.
Contoh berikut menunjukkan cara menerapkan langkah ini.
override fun onBeginCreateCredentialRequest(
request: BeginCreateCredentialRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException>,
) {
val response: BeginCreateCredentialResponse? = processCreateCredentialRequest(request)
if (response != null) {
callback.onResult(response)
} else {
callback.onError(CreateCredentialUnknownException())
}
}
fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse? {
when (request) {
is BeginCreatePublicKeyCredentialRequest -> {
// Request is passkey type
return handleCreatePasskeyQuery(request)
}
}
// Request not supported
return null
}
private fun handleCreatePasskeyQuery(
request: BeginCreatePublicKeyCredentialRequest
): BeginCreateCredentialResponse {
// Adding two create entries - one for storing credentials to the 'Personal'
// account, and one for storing them to the 'Family' account. These
// accounts are local to this sample app only.
val createEntries: MutableList<CreateEntry> = mutableListOf()
createEntries.add( CreateEntry(
PERSONAL_ACCOUNT_ID,
createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
))
createEntries.add( CreateEntry(
FAMILY_ACCOUNT_ID,
createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
))
return BeginCreateCredentialResponse(createEntries)
}
private fun createNewPendingIntent(accountId: String, action: String): PendingIntent {
val intent = Intent(action).setPackage(PACKAGE_NAME)
// Add your local account ID as an extra to the intent, so that when
// user selects this entry, the credential can be saved to this
// account
intent.putExtra(EXTRA_KEY_ACCOUNT_ID, accountId)
return PendingIntent.getActivity(
applicationContext, UNIQUE_REQ_CODE,
intent, (
PendingIntent.FLAG_MUTABLE
or PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
Konstruksi PendingIntent
Anda harus mematuhi hal berikut:
- Aktivitas yang sesuai harus disiapkan untuk menampilkan perintah, konfirmasi, atau pemilihan Biometrik yang diperlukan.
- Setiap data yang diperlukan oleh penyedia saat aktivitas terkait
dipanggil harus ditetapkan sebagai tambahan pada intent yang digunakan untuk membuat
PendingIntent
, sepertiaccountId
dalam alur pembuatan. PendingIntent
harus dibuat dengan flagPendingIntent.FLAG_MUTABLE
sehingga sistem dapat menambahkan permintaan final ke tambahan intent.PendingIntent
Anda tidak boleh dibuat dengan flagPendingIntent.FLAG_ONE_SHOT
karena pengguna dapat memilih entri, kembali, dan memilih ulang entri, yang akan menyebabkanPendingIntent
diaktifkan dua kali.PendingIntent
harus dibuat dengan kode permintaan unik agar setiap entri dapat memilikiPendingIntent
yang sesuai.
Menangani pemilihan entri untuk permintaan pembuatan kunci sandi
- Saat pengguna memilih
CreateEntry
yang sebelumnya diisi,PendingIntent
yang sesuai akan dipanggil danActivity
penyedia terkait akan dibuat. - Setelah metode
onCreate
Aktivitas dipanggil, akses intent terkait dan teruskan ke classPendingIntentHander
untuk mendapatkanProviderCreateCredentialRequest
. - Ekstrak
requestJson
,callingAppInfo
, danclientDataHash
dari permintaan. - Ekstrak
accountId
lokal dari ekstra intent. Ini adalah implementasi khusus aplikasi contoh dan tidak diperlukan. ID akun ini dapat digunakan untuk menyimpan kredensial terhadap ID akun ini. - Validasi
requestJson
. Contoh di bawah menggunakan class data lokal sepertiPublicKeyCredentialCreationOptions
untuk mengonversi JSON input menjadi class terstruktur sesuai spesifikasi WebAuthn. Sebagai penyedia kredensial, Anda dapat menggantinya dengan parser Anda sendiri. - Periksa link aset untuk aplikasi panggilan jika panggilan berasal dari aplikasi Android native.
- Tampilkan perintah autentikasi. Contoh di bawah ini menggunakan Android Biometric API.
- Setelah autentikasi berhasil, buat
credentialId
dan pasangan kunci. - Simpan kunci pribadi di database lokal Anda pada
callingAppInfo.packageName
. - Buat respons JSON Web Authentication API yang
terdiri dari kunci publik dan
credentialId
. Contoh di bawah ini menggunakan class utilitas lokal sepertiAuthenticatorAttestationResponse
danFidoPublicKeyCredential
yang membantu pembuatan JSON berdasarkan spesifikasi yang disebutkan sebelumnya. Sebagai penyedia kredensial, Anda dapat mengganti class ini dengan builder Anda sendiri. - Buat
CreatePublicKeyCredentialResponse
dengan JSON yang dihasilkan di atas. - Tetapkan
CreatePublicKeyCredentialResponse
sebagai tambahan diIntent
melaluiPendingIntentHander.setCreateCredentialResponse()
, dan tetapkan intent tersebut ke hasil Aktivitas. - Selesaikan Aktivitas.
Contoh kode di bawah ini menggambarkan langkah-langkah ini. Kode ini perlu ditangani di
class Aktivitas Anda setelah onCreate()
dipanggil.
val request =
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
if (request != null && request.callingRequest is CreatePublicKeyCredentialRequest) {
val publicKeyRequest: CreatePublicKeyCredentialRequest =
request.callingRequest as CreatePublicKeyCredentialRequest
createPasskey(
publicKeyRequest.requestJson,
request.callingAppInfo,
publicKeyRequest.clientDataHash,
accountId
)
}
fun createPasskey(
requestJson: String,
callingAppInfo: CallingAppInfo?,
clientDataHash: ByteArray?,
accountId: String?
) {
val request = PublicKeyCredentialCreationOptions(requestJson)
val biometricPrompt = BiometricPrompt(
this,
<executor>,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int, errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
// Generate a credentialId
val credentialId = ByteArray(32)
SecureRandom().nextBytes(credentialId)
// Generate a credential key pair
val spec = ECGenParameterSpec("secp256r1")
val keyPairGen = KeyPairGenerator.getInstance("EC");
keyPairGen.initialize(spec)
val keyPair = keyPairGen.genKeyPair()
// Save passkey in your database as per your own implementation
// Create AuthenticatorAttestationResponse object to pass to
// FidoPublicKeyCredential
val response = AuthenticatorAttestationResponse(
requestOptions = request,
credentialId = credentialId,
credentialPublicKey = getPublicKeyFromKeyPair(keyPair),
origin = appInfoToOrigin(callingAppInfo),
up = true,
uv = true,
be = true,
bs = true,
packageName = callingAppInfo.packageName
)
val credential = FidoPublicKeyCredential(
rawId = credentialId, response = response
)
val result = Intent()
val createPublicKeyCredResponse =
CreatePublicKeyCredentialResponse(credential.json())
// Set the CreateCredentialResponse as the result of the Activity
PendingIntentHandler.setCreateCredentialResponse(
result, createPublicKeyCredResponse
)
setResult(Activity.RESULT_OK, result)
finish()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Use your screen lock")
.setSubtitle("Create passkey for ${request.rp.name}")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
/* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
)
.build()
biometricPrompt.authenticate(promptInfo)
}
fun appInfoToOrigin(info: CallingAppInfo): String {
val cert = info.signingInfo.apkContentsSigners[0].toByteArray()
val md = MessageDigest.getInstance("SHA-256");
val certHash = md.digest(cert)
// This is the format for origin
return "android:apk-key-hash:${b64Encode(certHash)}"
}
Menangani kueri untuk permintaan pembuatan sandi
Untuk menangani kueri atas permintaan pembuatan sandi, lakukan hal berikut:
- Di dalam metode
processCreateCredentialRequest()
yang disebutkan di bagian sebelumnya, tambahkan kasus lain di dalam blok tombol untuk menangani permintaan sandi. - Saat membuat
BeginCreateCredentialResponse
, tambahkanCreateEntries
yang diperlukan. - Setiap
CreateEntry
harus sesuai dengan akun tempat kredensial dapat disimpan, dan harus memilikiPendingIntent
yang ditetapkan bersama dengan metadata lainnya.
Contoh berikut menunjukkan cara menerapkan langkah ini:
fun processCreateCredentialRequest(
request: BeginCreateCredentialRequest
): BeginCreateCredentialResponse? {
when (request) {
is BeginCreatePublicKeyCredentialRequest -> {
// Request is passkey type
return handleCreatePasskeyQuery(request)
}
is BeginCreatePasswordCredentialRequest -> {
// Request is password type
return handleCreatePasswordQuery(request)
}
}
return null
}
private fun handleCreatePasswordQuery(
request: BeginCreatePasswordCredentialRequest
): BeginCreateCredentialResponse {
val createEntries: MutableList<CreateEntry> = mutableListOf()
// Adding two create entries - one for storing credentials to the 'Personal'
// account, and one for storing them to the 'Family' account. These
// accounts are local to this sample app only.
createEntries.add(
CreateEntry(
PERSONAL_ACCOUNT_ID,
createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
)
)
createEntries.add(
CreateEntry(
FAMILY_ACCOUNT_ID,
createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
)
)
return BeginCreateCredentialResponse(createEntries)
}
Menangani pemilihan entri untuk permintaan pembuatan sandi
Saat pengguna memilih CreateEntry
yang terisi, PendingIntent
yang sesuai akan dieksekusi dan menampilkan Aktivitas terkait. Akses
intent terkait yang diteruskan di onCreate
dan teruskan ke
class PendingIntentHander
untuk mendapatkan metode ProviderCreateCredentialRequest
.
Contoh di bawah menggambarkan cara menerapkan proses ini. Kode ini perlu
ditangani dalam metode onCreate()
Aktivitas Anda.
val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest
// Fetch the ID and password from the request and save it in your database
<your_database>.addNewPassword(
PasswordInfo(
request.id,
request.password,
createRequest.callingAppInfo.packageName
)
)
//Set the final response back
val result = Intent()
val response = CreatePasswordResponse()
PendingIntentHandler.setCreateCredentialResponse(result, response)
setResult(Activity.RESULT_OK, result)
this@<activity>.finish()
Menangani login pengguna
Login pengguna ditangani dengan langkah-langkah berikut:
- Saat aplikasi klien mencoba membuat pengguna login, aplikasi tersebut akan menyiapkan
instance
GetCredentialRequest
. - Framework Android menyebarkan permintaan ini ke semua penyedia kredensial yang berlaku dengan mengikat ke layanan tersebut.
- Layanan penyedia kemudian menerima
BeginGetCredentialRequest
yang berisi daftarBeginGetCredentialOption
, yang masing-masing berisi parameter yang dapat digunakan untuk mengambil kredensial yang cocok.
Untuk menangani permintaan ini di layanan penyedia kredensial Anda, selesaikan langkah-langkah berikut:
Ganti metode
onBeginGetCredentialRequest()
untuk menangani permintaan. Perhatikan bahwa jika kredensial dikunci, Anda dapat segera menetapkanAuthenticationAction
pada respons dan memanggil callback.private val unlockEntryTitle = "Authenticate to continue" override fun onBeginGetCredentialRequest( request: BeginGetCredentialRequest, cancellationSignal: CancellationSignal, callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>, ) { if (isAppLocked()) { callback.onResult(BeginGetCredentialResponse( authenticationActions = mutableListOf(AuthenticationAction( unlockEntryTitle, createUnlockPendingIntent()) ) ) ) return } try { response = processGetCredentialRequest(request) callback.onResult(response) } catch (e: GetCredentialException) { callback.onError(GetCredentialUnknownException()) } }
Penyedia yang mewajibkan membuka kunci kredensial sebelum menampilkan
credentialEntries
apa pun harus menyiapkan intent tertunda yang mengarahkan pengguna ke alur buka kunci aplikasi:private fun createUnlockPendingIntent(): PendingIntent { val intent = Intent(UNLOCK_INTENT).setPackage(PACKAGE_NAME) return PendingIntent.getActivity( applicationContext, UNIQUE_REQUEST_CODE, intent, ( PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) ) }
Ambil kredensial dari database lokal Anda dan siapkan menggunakan
CredentialEntries
agar ditampilkan di pemilih. Untuk kunci sandi, Anda dapat menetapkancredentialId
sebagai tambahan pada intent guna mengetahui kredensial mana yang dipetakan saat pengguna memilih entri ini.companion object { // These intent actions are specified for corresponding activities // that are to be invoked through the PendingIntent(s) private const val GET_PASSKEY_INTENT_ACTION = "PACKAGE_NAME.GET_PASSKEY" private const val GET_PASSWORD_INTENT_ACTION = "PACKAGE_NAME.GET_PASSWORD" } fun processGetCredentialsRequest( request: BeginGetCredentialRequest ): BeginGetCredentialResponse { val callingPackage = request.callingAppInfo?.packageName val credentialEntries: MutableList<CredentialEntry> = mutableListOf() for (option in request.beginGetCredentialOptions) { when (option) { is BeginGetPasswordOption -> { credentialEntries.addAll( populatePasswordData( callingPackage, option ) ) } is BeginGetPublicKeyCredentialOption -> { credentialEntries.addAll( populatePasskeyData( callingPackage, option ) ) ) } else -> { Log.i(TAG, "Request not supported") } } } return BeginGetCredentialResponse(credentialEntries) }
Buat kueri kredensial dari database Anda, buat entri kunci sandi dan sandi untuk diisi.
private fun populatePasskeyData( callingAppInfo: CallingAppInfo, option: BeginGetPublicKeyCredentialOption ): List<CredentialEntry> { val passkeyEntries: MutableList<CredentialEntry> = mutableListOf() val request = PublicKeyCredentialRequestOptions(option.requestJson) // Get your credentials from database where you saved during creation flow val creds = <getCredentialsFromInternalDb(request.rpId)> val passkeys = creds.passkeys for (passkey in passkeys) { val data = Bundle() data.putString("credId", passkey.credId) passkeyEntries.add( PublicKeyCredentialEntry( context = applicationContext, username = passkey.username, pendingIntent = createNewPendingIntent( GET_PASSKEY_INTENT_ACTION, data ), beginPublicKeyCredentialOption = option, displayName = passkey.displayName, icon = passkey.icon ) ) } return passkeyEntries } // Fetch password credentials and create password entries to populate to // the user private fun populatePasswordData( callingPackage: String, option: BeginGetPasswordOption ): List<CredentialEntry> { val passwordEntries: MutableList<CredentialEntry> = mutableListOf() // Get your password credentials from database where you saved during // creation flow val creds = <getCredentialsFromInternalDb(callingPackage)> val passwords = creds.passwords for (password in passwords) { passwordEntries.add( PasswordCredentialEntry( context = applicationContext, username = password.username, pendingIntent = createNewPendingIntent( GET_PASSWORD_INTENT ), beginGetPasswordOption = option displayName = password.username, icon = password.icon ) ) } return passwordEntries } private fun createNewPendingIntent( action: String, extra: Bundle? = null ): PendingIntent { val intent = Intent(action).setPackage(PACKAGE_NAME) if (extra != null) { intent.putExtra("CREDENTIAL_DATA", extra) } return PendingIntent.getActivity( applicationContext, UNIQUE_REQUEST_CODE, intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) ) }
Setelah Anda membuat kueri dan mengisi kredensial, kini Anda perlu menangani fase pemilihan untuk kredensial yang dipilih pengguna, baik dengan kunci sandi maupun sandi.
Menangani pemilihan pengguna untuk kunci sandi
- Dalam metode
onCreate
Aktivitas yang sesuai, ambil intent terkait, dan teruskan kePendingIntentHandler.retrieveProviderGetCredentialRequest()
. - Ekstrak
GetPublicKeyCredentialOption
dari permintaan yang diambil di atas. Selanjutnya, ekstrakrequestJson
danclientDataHash
dari opsi ini. - Ekstrak
credentialId
dari ekstra intent yang diisi oleh penyedia kredensial saatPendingIntent
yang sesuai disiapkan. - Ekstrak kunci sandi dari database lokal menggunakan parameter permintaan yang diakses di atas.
Nyatakan bahwa kunci sandi valid dengan metadata yang diekstrak dan verifikasi pengguna.
val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val publicKeyRequest = getRequest.credentialOption as GetPublicKeyCredentialOption val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA") val credIdEnc = requestInfo.getString("credId") // Get the saved passkey from your database based on the credential ID // from the publickeyRequest val passkey = <your database>.getPasskey(credIdEnc) // Decode the credential ID, private key and user ID val credId = b64Decode(credIdEnc) val privateKey = b64Decode(passkey.credPrivateKey) val uid = b64Decode(passkey.uid) val origin = appInfoToOrigin(getRequest.callingAppInfo) val packageName = getRequest.callingAppInfo.packageName validatePasskey( publicKeyRequest.requestJson, origin, packageName, uid, passkey.username, credId, privateKey )
Untuk memvalidasi pengguna, tampilkan perintah Biometrik (atau metode pernyataan lainnya). Cuplikan kode di bawah ini menggunakan Android Biometric API.
Setelah autentikasi berhasil, buat respons JSON berdasarkan spesifikasi W3 Web Authentication Assertion. Dalam cuplikan kode di bawah ini, class data helper seperti
AuthenticatorAssertionResponse
digunakan untuk mengambil parameter terstruktur dan mengonversinya menjadi format JSON yang diperlukan. Respons berisi tanda tangan digital dari kunci pribadi kredensial WebAuthn. Server pihak tepercaya dapat memverifikasi tanda tangan ini untuk mengautentikasi pengguna sebelum login.Buat
PublicKeyCredential
menggunakan JSON yang dihasilkan di atas lalu tetapkan padaGetCredentialResponse
akhir. Tetapkan respons akhir ini pada hasil aktivitas ini.
Contoh berikut menggambarkan cara menerapkan langkah-langkah ini:
val request = PublicKeyCredentialRequestOptions(requestJson)
val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)
val biometricPrompt = BiometricPrompt(
this,
<executor>,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int, errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
val response = AuthenticatorAssertionResponse(
requestOptions = request,
credentialId = credId,
origin = origin,
up = true,
uv = true,
be = true,
bs = true,
userHandle = uid,
packageName = packageName
)
val sig = Signature.getInstance("SHA256withECDSA");
sig.initSign(privateKey)
sig.update(response.dataToSign())
response.signature = sig.sign()
val credential = FidoPublicKeyCredential(
rawId = credId, response = response
)
val result = Intent()
val passkeyCredential = PublicKeyCredential(credential.json)
PendingIntentHandler.setGetCredentialResponse(
result, GetCredentialResponse(passkeyCredential)
)
setResult(RESULT_OK, result)
finish()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Use your screen lock")
.setSubtitle("Use passkey for ${request.rpId}")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
/* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
)
.build()
biometricPrompt.authenticate(promptInfo)
Menangani pemilihan pengguna untuk autentikasi sandi
- Di aktivitas yang sesuai, akses intent yang diteruskan ke
onCreate
dan ekstrakProviderGetCredentialRequest
menggunakanPendingIntentHandler
. Gunakan
GetPasswordOption
dalam permintaan untuk mengambil kredensial sandi untuk nama paket yang masuk.val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val passwordOption = getRequest.credentialOption as GetPasswordCredentialOption val username = passwordOption.username // Fetch the credentials for the calling app package name val creds = <your_database>.getCredentials(callingAppInfo.packageName) val passwords = creds.passwords val it = passwords.iterator() var password = "" while (it.hasNext() == true) { val passwordItemCurrent = it.next() if (passwordItemCurrent.username == username) { password = passwordItemCurrent.password break } }
Setelah diambil, tetapkan respons untuk kredensial sandi yang dipilih.
// Set the response back val result = Intent() val passwordCredential = PasswordCredential(username, password) PendingIntentHandler.setGetCredentialResponse( result, GetCredentialResponse(passwordCredential) ) setResult(Activity.RESULT_OK, result) finish()
Menangani pemilihan entri tindakan autentikasi
Seperti disebutkan sebelumnya, penyedia kredensial dapat menetapkan
AuthenticationAction
jika kredensial dikunci. Jika pengguna memilih
entri ini, Aktivitas yang sesuai dengan tindakan intent yang ditetapkan di
PendingIntent
akan dipanggil. Penyedia kredensial kemudian dapat menampilkan alur autentikasi
biometrik atau mekanisme serupa untuk membuka kredensial. Jika berhasil,
penyedia kredensial harus membuat BeginGetCredentialResponse
, mirip
dengan cara penanganan login pengguna dijelaskan di atas, karena kredensial sekarang
terbuka. Respons ini kemudian harus ditetapkan melalui
metode PendingIntentHandler.setBeginGetCredentialResponse()
sebelum
intent yang disiapkan ditetapkan sebagai hasilnya dan Aktivitas selesai.
Menghapus permintaan kredensial
Aplikasi klien dapat meminta agar setiap status yang dipertahankan untuk pemilihan kredensial harus
dihapus, seperti penyedia kredensial dapat mengingat kredensial yang dipilih
sebelumnya dan hanya menampilkannya di lain waktu. Aplikasi klien memanggil API ini dan
mengharapkan pemilihan melekat tersebut akan dihapus. Layanan penyedia kredensial Anda
dapat menangani permintaan ini dengan mengganti metode
onClearCredentialStateRequest()
:
override fun onClearCredentialStateRequest(
request: android.service.credentials.ClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>,
) {
// Delete any maintained state as appropriate.
}
Tambahkan kemampuan untuk menautkan ke halaman setelan penyedia Anda
Untuk mengizinkan pengguna membuka setelan penyedia Anda dari Passwords,
kunci sandi, & isi otomatis, aplikasi penyedia kredensial harus menerapkan
Atribut manifes credential-provider
settingsActivity
di
res/xml/provider.xml
. Atribut ini memungkinkan Anda menggunakan intent untuk membuka layar
setelan aplikasi Anda sendiri jika pengguna mengklik nama penyedia dalam daftar layanan Sandi,
kunci sandi, & isi otomatis. Tetapkan nilai atribut ini ke
nama aktivitas yang akan diluncurkan dari layar pengaturan.
<credential-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsSubtitle="Example settings provider name"
android:settingsActivity="com.example.SettingsActivity">
<capabilities>
<capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>
Intent setelan
Buka setelan: Intent android.settings.CREDENTIAL_PROVIDER
akan menampilkan layar setelan tempat pengguna dapat memilih penyedia kredensial tambahan dan
pilihan mereka.
Layanan kredensial pilihan: Intent
ACTION_REQUEST_SET_AUTOFILL_SERVICE
mengalihkan pengguna ke
layar pemilihan penyedia pilihan. Penyedia yang dipilih di layar ini
menjadi kredensial dan penyedia isi otomatis pilihan.
Mendapatkan daftar aplikasi dengan hak istimewa yang diizinkan
Aplikasi dengan hak istimewa seperti browser web melakukan panggilan Credential Manager atas nama
pihak tepercaya lainnya dengan menetapkan parameter origin
di Credential Manager GetCredentialRequest()
dan
metode CreatePublicKeyCredentialRequest()
. Untuk memproses permintaan ini,
penyedia kredensial mengambil origin
menggunakan getOrigin()
API.
Untuk mengambil origin
, aplikasi penyedia kredensial harus meneruskan daftar
pemanggil dengan hak istimewa dan tepercaya ke
androidx.credentials.provider.CallingAppInfo's getOrigin()
API. Daftar yang diizinkan ini
harus berupa objek JSON yang valid. origin
akan ditampilkan jika packageName
dan
sidik jari sertifikat yang didapatkan dari signingInfo
cocok dengan aplikasi
yang ditemukan di privilegedAllowlist
yang diteruskan ke getOrigin()
API. Setelah
Nilai origin
diperoleh, aplikasi penyedia harus menganggapnya sebagai hak istimewa
memanggil dan menetapkan origin
ini pada data klien
di AuthenticatorResponse
, alih-alih menghitung
origin
menggunakan tanda tangan aplikasi pemanggil.
Jika Anda mengambil origin
, gunakan clientDataHash
yang disediakan langsung
di CreatePublicKeyCredentialRequest()
atau
GetPublicKeyCredentialOption()
, bukan menyusun dan melakukan hashing
clientDataJSON
selama permintaan tanda tangan. Untuk menghindari masalah penguraian JSON,
tetapkan nilai placeholder untuk clientDataJSON
dalam respons
pengesahan dan pernyataan.
Pengelola Sandi Google menggunakan daftar yang diizinkan yang tersedia secara terbuka untuk
panggilan ke getOrigin()
. Sebagai penyedia kredensial, Anda dapat menggunakan daftar ini atau
memberikan daftar Anda sendiri dalam format JSON yang dijelaskan oleh API. Penyedia
dapat memilih daftar mana yang akan digunakan. Untuk mendapatkan akses hak istimewa dengan
penyedia kredensial pihak ketiga, lihat dokumentasi yang disediakan oleh pihak ketiga.
Mengaktifkan penyedia di perangkat
Pengguna harus mengaktifkan penyedia melalui device settings > Passwords & Accounts > Your Provider > Enable or Disable.
fun createSettingsPendingIntent(): PendingIntent