Kimlik Bilgisi Yöneticisi'ni, kimlik bilgisi sağlayıcı çözümünüzle entegre edin

Kimlik Bilgisi Yöneticisi, Android 14'te kullanıma sunulan ve kullanıcı adı-şifre, geçiş anahtarları ve federasyon oturum açma çözümleri (ör. Google ile oturum açma) gibi birden fazla oturum açma yöntemini destekleyen bir API'ler kümesini ifade eder. Kimlik Bilgisi Yöneticisi API'si çağrıldığında Android sistemi, cihazda yüklü tüm kimlik bilgisi sağlayıcılarından kimlik bilgilerini toplar. Bu belgede, bu kimlik bilgisi sağlayıcıları için entegrasyon uç noktaları sağlayan API'ler açıklanmaktadır.

Kurulum

Kimlik bilgisi sağlayıcınızda işlevselliği uygulamadan önce aşağıdaki bölümlerde gösterilen kurulum adımlarını tamamlayın.

Bağımlılıkları bildirme

Modülünüzün build.gradle dosyasında, Credential Manager kitaplığının en son sürümünü kullanarak bağımlılık beyan edin:

implementation "androidx.credentials:credentials:1.2.0-{latest}"

Manifest dosyasında hizmet öğesini tanımlayın

Uygulamanızın manifest dosyasına AndroidManifest.xml, aşağıdaki örnekte gösterildiği gibi androidx.credentials kitaplığındaki CredentialProviderService sınıfını genişleten bir hizmet sınıfı için <service> beyanı ekleyin.

<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"
    tools:targetApi="upside_down_cake">
    <intent-filter>
        <action android:name="android.service.credentials.CredentialProviderService"/>
    </intent-filter>
    <meta-data
        android:name="android.credentials.provider"
        android:resource="@xml/provider"/>
</service>

Önceki örnekte gösterilen izin ve amaç filtresi, Kimlik Bilgisi Yöneticisi akışının beklendiği gibi çalışması için gereklidir. Bu hizmete yalnızca Android sisteminin bağlanabilmesi için izin gerekir. Amaç filtresi, bu hizmetin Kimlik Bilgileri Yöneticisi tarafından kullanılacak bir kimlik bilgisi sağlayıcı olarak bulunabilirliği için kullanılır.

Desteklenen kimlik bilgisi türlerini bildirme

res/xml dizininizde provider.xml adlı yeni bir dosya oluşturun. Bu dosyada, hizmetinizin desteklediği kimlik bilgisi türlerini kitaplıktaki her kimlik bilgisi türü için tanımlanan sabitler aracılığıyla bildirin. Aşağıdaki örnekte, hizmet hem geleneksel şifreleri hem de geçiş anahtarlarını desteklemektedir. Geçiş anahtarlarının sabitleri TYPE_PASSWORD_CREDENTIAL ve TYPE_PUBLIC_KEY_CREDENTIAL olarak tanımlanmıştır:

<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>

Önceki API düzeylerinde kimlik bilgisi sağlayıcılar, şifreler ve diğer veriler için otomatik doldurma gibi API'lerle entegre olur. Bu sağlayıcılar, mevcut kimlik bilgisi türlerini depolamak için aynı dahili altyapıyı kullanabilir ve geçiş anahtarları da dahil olmak üzere diğerlerini desteklemek için altyapıyı genişletebilir.

Sağlayıcı etkileşimine yönelik iki aşamalı yaklaşım

Kimlik bilgisi yöneticisi, kimlik bilgisi sağlayıcılarla iki aşamada etkileşim kurar:

  1. İlk aşama, sistemin kimlik bilgisi sağlayıcı hizmetlerine bağlandığı ve Begin… istekleriyle onBeginGetCredentialRequest(), onBeginCreateCredentialRequest() veya onClearCredentialStateRequest() yöntemlerini çağırdığı başlatma/sorgu aşamasıdır. Sağlayıcılar bu istekleri işlemeli ve Begin… yanıtlarıyla yanıtlamalıdır. Bu yanıtları, hesap seçicide gösterilecek görsel seçenekleri temsil eden girişlerle doldurmalıdır. Her girişin bir PendingIntent kümesi olmalıdır.
  2. Kullanıcı bir giriş seçtiğinde seçim aşaması başlar ve girişle ilişkili PendingIntent tetiklenerek ilgili sağlayıcı etkinliği gösterilir. Kullanıcı bu etkinlikle etkileşimi tamamladığında kimlik bilgisi sağlayıcı, etkinliği sonlandırmadan önce yanıtı etkinliğin sonucu olarak ayarlamalıdır. Bu yanıt, Credential Manager'ı çağıran istemci uygulamasına gönderilir.

Geçiş anahtarı oluşturma işlemini gerçekleştirme

Geçiş anahtarı oluşturmayla ilgili sorguları işleme

Bir istemci uygulaması geçiş anahtarı oluşturmak ve bunu bir kimlik bilgisi sağlayıcısında saklamak istediğinde createCredential API'sini çağırır. Bu isteği kimlik bilgisi sağlayıcı hizmetinizde, geçiş anahtarının depolama alanınızda saklanmasını sağlayacak şekilde işlemek için aşağıdaki bölümlerde gösterilen adımları tamamlayın.

  1. CredentialProviderService'dan genişletilmiş hizmetinizde onBeginCreateCredentialRequest() yöntemini geçersiz kılın.
  2. BeginCreateCredentialRequest değerini, karşılık gelen bir BeginCreateCredentialResponse oluşturup geri çağırmadan geçirerek işleyin.
  3. BeginCreateCredentialResponse oluştururken gerekli CreateEntries ekleyin. Her CreateEntry, kimlik bilgisinin kaydedilebileceği bir hesaba karşılık gelmeli ve diğer gerekli meta verilerle birlikte PendingIntent ayarlanmış olmalıdır.

Aşağıdaki örnekte bu adımların nasıl uygulanacağı gösterilmektedir.

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
            )
    )
}

PendingIntent yapınız aşağıdakilere uymalıdır:

  • İlgili etkinlik, gerekli biyometrik istemleri, onayları veya seçimleri gösterecek şekilde ayarlanmalıdır.
  • Sağlayıcının, ilgili etkinlik çağrıldığında ihtiyaç duyduğu tüm zorunlu veriler, PendingIntent oluşturmak için kullanılan amaçta ek olarak ayarlanmalıdır. Örneğin, oluşturma akışında accountId.
  • Sistemin son isteği amaç ekstra bilgisine ekleyebilmesi için PendingIntent, PendingIntent.FLAG_MUTABLE işaretiyle oluşturulmalıdır.
  • Kullanıcı bir girişi seçip geri dönerek tekrar seçebileceğinden PendingIntent, PendingIntent.FLAG_ONE_SHOT işaretiyle oluşturulmamalıdır. Bu durumda PendingIntent iki kez tetiklenir.
  • Her girişin kendi PendingIntent'si olabilmesi için PendingIntent'niz benzersiz bir istek koduyla oluşturulmalıdır.

Geçiş anahtarı oluşturma istekleri için giriş seçimini işleme

  1. Kullanıcı daha önce doldurulmuş bir CreateEntry seçtiğinde, ilgili PendingIntent çağrılır ve ilişkili sağlayıcı Activity oluşturulur.
  2. Etkinliğinizin onCreate yöntemi çağrıldıktan sonra ilişkili amaca erişin ve ProviderCreateCredentialRequest değerini almak için bu amacı PendingIntentHander sınıfına iletin.
  3. İstekten requestJson, callingAppInfo ve clientDataHash değerlerini ayıklayın.
  4. Amacın ekstrasından yerel accountId değerini çıkarın. Bu, uygulamaya özel bir örnek uygulamadır ve zorunlu değildir. Bu hesap kimliği, bu kimlik bilgisini söz konusu hesap kimliğine göre saklamak için kullanılabilir.
  5. requestJson öğesini doğrulayın. Aşağıdaki örnekte, giriş JSON'unu WebAuthn spesifikasyonuna göre yapılandırılmış bir sınıfa dönüştürmek için PublicKeyCredentialCreationOptions gibi yerel veri sınıfları kullanılmaktadır. Kimlik bilgisi sağlayıcı olarak bunu kendi ayrıştırıcınızla değiştirebilirsiniz.
  6. Arama yerel bir Android uygulamasından yapılıyorsa arama uygulaması için asset-link'i kontrol edin.
  7. Kimlik doğrulama istemi gösterin. Aşağıdaki örnekte Android Biometric API'si kullanılmaktadır.
  8. Kimlik doğrulama başarılı olduğunda credentialId ve anahtar çifti oluşturun.
  9. Özel anahtarı yerel veritabanınıza callingAppInfo.packageName karşı kaydedin.
  10. Web Authentication API JSON yanıtı oluşturun. Bu yanıt, ortak anahtardan ve credentialId'den oluşur. Aşağıdaki örnekte, daha önce bahsedilen spesifikasyona dayalı bir JSON oluşturmaya yardımcı olan AuthenticatorAttestationResponse ve FidoPublicKeyCredential gibi yerel yardımcı sınıf kullanılıyor.Kimlik bilgisi sağlayıcı olarak bu sınıfları kendi oluşturucularınızla değiştirebilirsiniz.
  11. Yukarıda oluşturulan JSON ile bir CreatePublicKeyCredentialResponse oluşturun.
  12. CreatePublicKeyCredentialResponse öğesini Intent aracılığıyla PendingIntentHander.setCreateCredentialResponse() üzerinde ek olarak ayarlayın ve bu amacı Etkinlik sonucuna göre belirleyin.
  13. Etkinliği tamamlayın.

Aşağıdaki kod örneğinde bu adımlar gösterilmektedir. Bu kod, onCreate() çağrıldıktan sonra Etkinlik sınıfınızda işlenmelidir.

override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
    super.onCreate(savedInstanceState, persistentState)
    // ...

    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
        )
    }
}

@SuppressLint("RestrictedApi")
fun createPasskey(
    requestJson: String,
    callingAppInfo: CallingAppInfo?,
    clientDataHash: ByteArray?,
    accountId: String?
) {
    val request = PublicKeyCredentialCreationOptions(requestJson)

    val biometricPrompt = BiometricPrompt(
        this,
        {  }, // Pass in your own executor
        object : AuthenticationCallback() {
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                super.onAuthenticationError(errorCode, errString)
                finish()
            }

            override fun onAuthenticationFailed() {
                super.onAuthenticationFailed()
                finish()
            }

            @RequiresApi(VERSION_CODES.P)
            override fun onAuthenticationSucceeded(
                result: 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,
                    authenticatorAttachment = "", // Add your authenticator attachment
                )
                val result = Intent()

                val createPublicKeyCredResponse =
                    CreatePublicKeyCredentialResponse(credential.json())

                // Set the CreateCredentialResponse as the result of the Activity
                PendingIntentHandler.setCreateCredentialResponse(
                    result,
                    createPublicKeyCredResponse
                )
                setResult(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)
}

@RequiresApi(VERSION_CODES.P)
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)}"
}

Şifre oluşturma istekleriyle ilgili sorguları işleme

Şifre oluşturma istekleriyle ilgili sorguları işlemek için aşağıdakileri yapın:

  • Önceki bölümde belirtilen processCreateCredentialRequest() yönteminizin içinde, şifre isteklerini işlemek için switch bloğuna başka bir durum ekleyin.
  • BeginCreateCredentialResponse oluştururken gerekli CreateEntries ekleyin.
  • Her CreateEntry, kimlik bilgisinin kaydedilebileceği bir hesaba karşılık gelmeli ve diğer meta verilerle birlikte PendingIntent ayarlanmış olmalıdır.

Aşağıdaki örnekte bu adımların nasıl uygulanacağı gösterilmektedir:

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
}

@RequiresApi(VERSION_CODES.M)
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)
}

Şifre oluşturma istekleri için giriş seçimini işleme

Kullanıcı, doldurulmuş bir CreateEntry seçtiğinde ilgili PendingIntent yürütülür ve ilişkili etkinlik gösterilir. onCreate içinde iletilen ilişkili amaca erişin ve ProviderCreateCredentialRequest yöntemini almak için PendingIntentHander sınıfına iletin.

Aşağıdaki örnekte bu sürecin nasıl uygulanacağı gösterilmektedir. Bu kod, etkinliğinizin onCreate() yönteminde işlenmelidir.

val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)

if (createRequest == null) {
    return
}

val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest

// Fetch the ID and password from the request and save it in your database
mDatabase.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)
finish()

Kullanıcı oturum açma işlemlerini yönetme

Kullanıcı oturum açma işlemi aşağıdaki adımlarla gerçekleştirilir:

  • Bir istemci uygulaması kullanıcının oturumunu açmaya çalıştığında GetCredentialRequest örneği hazırlar.
  • Android çerçevesi, bu hizmetlere bağlanarak bu isteği geçerli tüm kimlik bilgisi sağlayıcılara yayar.
  • Daha sonra sağlayıcı hizmeti, her biri eşleşen kimlik bilgilerini almak için kullanılabilecek parametreler içeren BeginGetCredentialOption listesini içeren bir BeginGetCredentialRequest alır.

Bu isteği kimlik bilgisi sağlayıcı hizmetinizde işlemek için aşağıdaki adımları tamamlayın:

  1. İsteği işlemek için onBeginGetCredentialRequest() yöntemini geçersiz kılın. Kimlik bilgileriniz kilitliyse yanıta hemen bir AuthenticationAction ayarlayabileceğinizi ve geri çağırmayı tetikleyebileceğinizi unutmayın.

    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())
        }
    }
    

    Herhangi bir credentialEntries döndürmeden önce kimlik bilgilerinin kilidinin açılmasını gerektiren sağlayıcılar, kullanıcıyı uygulamanın kilit açma akışına yönlendiren bekleyen bir amaç oluşturmalıdır:

    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
                )
        )
    }
    
  2. Yerel veritabanınızdan kimlik bilgilerini alın ve seçicide gösterilecek şekilde CredentialEntries kullanarak ayarlayın. Geçiş anahtarları için, kullanıcı bu girişi seçtiğinde hangi kimlik bilgisiyle eşlendiğini bilmek amacıyla niyet üzerinde ek olarak credentialId ayarlayabilirsiniz.

    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 processGetCredentialRequest(
        request: BeginGetCredentialRequest
    ): BeginGetCredentialResponse {
        val callingPackageInfo = request.callingAppInfo
        val callingPackageName = callingPackageInfo?.packageName.orEmpty()
        val credentialEntries: MutableList<CredentialEntry> = mutableListOf()
    
        for (option in request.beginGetCredentialOptions) {
            when (option) {
                is BeginGetPasswordOption -> {
                    credentialEntries.addAll(
                        populatePasswordData(
                            callingPackageName,
                            option
                        )
                    )
                }
                is BeginGetPublicKeyCredentialOption -> {
                    credentialEntries.addAll(
                        populatePasskeyData(
                            callingPackageInfo,
                            option
                        )
                    )
                } else -> {
                    Log.i(TAG, "Request not supported")
                }
            }
        }
        return BeginGetCredentialResponse(credentialEntries)
    }
    
  3. Veritabanınızdaki kimlik bilgilerini sorgulayın, doldurmak için geçiş anahtarı ve şifre girişleri oluşturun.

    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
                    ),
                    beginGetPublicKeyCredentialOption = 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)
        )
    }
    
  4. Kimlik bilgilerini sorgulayıp doldurduktan sonra, kullanıcının seçtiği kimlik bilgilerinin (geçiş anahtarı veya şifre) seçim aşamasını yönetmeniz gerekir.

Geçiş anahtarları için kullanıcı seçimini işleme

  1. İlgili Etkinliğin onCreate yönteminde, ilişkili amaçları alın ve PendingIntentHandler.retrieveProviderGetCredentialRequest()'ye iletin.
  2. Yukarıda alınan istekten GetPublicKeyCredentialOption değerini çıkarın. Ardından, bu seçenekteki requestJson ve clientDataHash değerlerini çıkarın.
  3. İlgili PendingIntent ayarlandığında kimlik bilgisi sağlayıcı tarafından doldurulan amaç ekstra bilgisinden credentialId bilgisini ayıklayın.
  4. Yukarıda erişilen istek parametrelerini kullanarak yerel veritabanınızdan geçiş anahtarını ayıklayın.
  5. Geçiş anahtarının, çıkarılan meta veriler ve kullanıcı doğrulamasıyla geçerli olduğunu onaylayın.

    val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
    val publicKeyRequest = getRequest?.credentialOptions?.first() as GetPublicKeyCredentialOption
    
    val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA")
    val credIdEnc = requestInfo?.getString("credId").orEmpty()
    
    // Get the saved passkey from your database based on the credential ID from the PublicKeyRequest
    val passkey = mDatabase.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
    )
    
  6. Kullanıcıyı doğrulamak için biyometrik istem (veya başka bir onaylama yöntemi) gösterin. Aşağıdaki kod snippet'i Android Biometric API'yi kullanır.

  7. Kimlik doğrulama başarılı olduktan sonra W3 Web Authentication Assertion spesifikasyonuna göre bir JSON yanıtı oluşturun. Aşağıdaki kod snippet'inde, yapılandırılmış parametreleri alıp gerekli JSON biçimine dönüştürmek için AuthenticatorAssertionResponse gibi yardımcı veri sınıfları kullanılır. Yanıt, WebAuthn kimlik bilgisinin özel anahtarından alınan bir dijital imza içeriyor. Güvenen tarafın sunucusu, oturum açmadan önce kullanıcıyı doğrulamak için bu imzayı doğrulayabilir.

  8. Yukarıda oluşturulan JSON'u kullanarak bir PublicKeyCredential oluşturun ve bunu nihai bir GetCredentialResponse üzerine ayarlayın. Bu nihai yanıtı, bu etkinliğin sonucuna göre ayarlayın.

Aşağıdaki örnekte bu adımların nasıl uygulanabileceği gösterilmektedir:

val request = PublicKeyCredentialRequestOptions(requestJson)
val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)

val biometricPrompt = BiometricPrompt(
    this,
    {  }, // Pass in your own 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,
                authenticatorAttachment = "", // Add your authenticator attachment
            )
            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)

Şifre kimlik doğrulaması için kullanıcı seçimini işleme

  1. İlgili etkinliğinizde, onCreate'ya iletilen amaca erişin ve PendingIntentHandler kullanarak ProviderGetCredentialRequest değerini çıkarın.
  2. Gelen paket adının şifre kimlik bilgilerini almak için istekte GetPasswordOption kullanın.

    val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
    
    val passwordOption = getRequest?.credentialOptions?.first() as GetPasswordOption
    
    val username = passwordOption.allowedUserIds.first()
    // Fetch the credentials for the calling app package name
    val creds = mDatabase.getCredentials(callingAppInfo.packageName)
    val passwords = creds.passwords
    val it = passwords.iterator()
    var password = ""
    while (it.hasNext()) {
        val passwordItemCurrent = it.next()
        if (passwordItemCurrent.username == username) {
            password = passwordItemCurrent.password
            break
        }
    }
    
  3. Alındıktan sonra, seçilen şifre kimliği için yanıtı ayarlayın.

    // Set the response back
    val result = Intent()
    val passwordCredential = PasswordCredential(username, password)
    PendingIntentHandler.setGetCredentialResponse(
        result, GetCredentialResponse(passwordCredential)
    )
    setResult(Activity.RESULT_OK, result)
    finish()
    

Kimlik doğrulama işlemi girişinin seçilmesini işleme

Daha önce belirtildiği gibi, kimlik bilgileri kilitliyse kimlik bilgisi sağlayıcı bir AuthenticationAction ayarlayabilir. Kullanıcı bu girişi seçerse PendingIntent içinde ayarlanan amaç işlemine karşılık gelen Etkinlik çağrılır. Kimlik bilgisi sağlayıcılar, kimlik bilgilerinin kilidini açmak için biyometrik kimlik doğrulama akışı veya benzer bir mekanizma gösterebilir. Başarılı olduğunda, kimlik bilgileri artık kilidi açılmış olduğundan kimlik bilgisi sağlayıcı, kullanıcı oturum açma işleminin yukarıda açıklandığı şekilde BeginGetCredentialResponse oluşturmalıdır. Bu yanıt, hazırlanmış amaç sonuç olarak ayarlanıp Etkinlik tamamlanmadan önce PendingIntentHandler.setBeginGetCredentialResponse() yöntemiyle ayarlanmalıdır.

Kimlik bilgisi isteklerini temizleme

Bir istemci uygulaması, kimlik seçimi için korunan tüm durumların temizlenmesini isteyebilir. Örneğin, bir kimlik sağlayıcı daha önce seçilen kimliği hatırlayabilir ve bir sonraki sefer yalnızca bu kimliği döndürebilir. Bir istemci uygulaması bu API'yi çağırır ve yapışkan seçimin temizlenmesini bekler. Kimlik bilgisi sağlayıcı hizmetiniz, onClearCredentialStateRequest() yöntemini geçersiz kılarak bu isteği işleyebilir:

override fun onClearCredentialStateRequest(
    request: ProviderClearCredentialStateRequest,
    cancellationSignal: CancellationSignal,
    callback: OutcomeReceiver<Void?, ClearCredentialException>
) {
    // Delete any maintained state as appropriate.
}

Kullanıcılarınızın, sağlayıcınızın ayarlarını Şifreler, geçiş anahtarları ve otomatik doldurma ekranından açmasına izin vermek için kimlik bilgisi sağlayıcı uygulamaların res/xml/provider.xml içinde credential-provider settingsActivity manifest özelliğini uygulaması gerekir. Bu özellik, bir kullanıcı Şifreler, geçiş anahtarları ve otomatik doldurma hizmetleri listesinde bir sağlayıcı adını tıkladığında uygulamanızın kendi ayarlar ekranını açmak için bir amaç kullanmanıza olanak tanır. Bu özelliğin değerini, ayarlar ekranından başlatılacak etkinliğin adı olarak ayarlayın.

<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>
Değiştirme ve açma düğmesi işlevlerini gösteren şema
Şekil 1: Değiştir düğmesi, mevcut seçim iletişim kutusunu açarak kullanıcının tercih ettiği kimlik bilgisi sağlayıcıyı seçmesine olanak tanır. düğmesi, manifest değişikliğinde tanımlanan ayarlar etkinliğini başlatır ve özellikle bu sağlayıcıya yönelik bir ayarlar sayfası açar.

Ayarlar amaçları

Ayarları aç: android.settings.CREDENTIAL_PROVIDER amaçlı işlemi, kullanıcının tercih ettiği ve ek kimlik bilgisi sağlayıcılarını seçebileceği bir ayarlar ekranını açar.

Şifreler, geçiş anahtarları ve otomatik doldurma ayarları ekranı
Şekil 2: Şifreler, geçiş anahtarları ve otomatik doldurma ayarları ekranı.

Tercih edilen kimlik bilgisi hizmeti: ACTION_REQUEST_SET_AUTOFILL_SERVICE amaçlı işlemi, kullanıcınızı tercih edilen sağlayıcı seçim ekranına yönlendirir. Bu ekranda seçilen sağlayıcı, tercih edilen kimlik bilgileri ve otomatik doldurma sağlayıcısı olur.

Değiştirme ve açma düğmesi işlevlerini gösteren şema
Şekil 3: Şifreler, geçiş anahtarları ve otomatik doldurma için tercih edilen hizmet ayarları ekranı.

Ayrıcalıklı uygulamaların izin verilenler listesini edinme

Web tarayıcıları gibi ayrıcalıklı uygulamalar, GetCredentialRequest() ve CreatePublicKeyCredentialRequest() yöntemlerinde origin parametresini ayarlayarak diğer güvenen taraflar adına kimlik bilgisi yöneticisi çağrıları yapar. Kimlik bilgisi sağlayıcı, bu istekleri işlemek için getOrigin() API'sini kullanarak origin değerini alır.

origin değerini almak için kimlik bilgisi sağlayıcı uygulamanın, androidx.credentials.provider.CallingAppInfo's getOrigin() API'sine ayrıcalıklı ve güvenilir arayanların listesini iletmesi gerekir. Bu izin verilenler listesi geçerli bir JSON nesnesi olmalıdır. packageName ve signingInfo'den alınan sertifika parmak izleri, privilegedAllowlist'de bulunan ve getOrigin() API'ye iletilen bir uygulamanın parmak izleriyle eşleşirse origin döndürülür. origin değeri alındıktan sonra sağlayıcı uygulama bunu ayrıcalıklı bir çağrı olarak değerlendirmeli ve origin değerini, çağıran uygulamanın imzasını kullanarak hesaplamak yerine AuthenticatorResponse içindeki istemci verilerine ayarlamalıdır.origin

Bir origin alırsanız imza isteği sırasında clientDataJSON oluşturup karma oluşturmak yerine doğrudan CreatePublicKeyCredentialRequest() veya GetPublicKeyCredentialOption() içinde sağlanan clientDataHash kullanın. JSON ayrıştırma sorunlarını önlemek için onay ve onay yanıtında clientDataJSON için yer tutucu bir değer ayarlayın. Google Şifre Yöneticisi, getOrigin()'ye yapılan aramalar için herkese açık bir izin verilenler listesi kullanır. Kimlik bilgisi sağlayıcı olarak bu listeyi kullanabilir veya API tarafından açıklanan JSON biçiminde kendi listenizi sağlayabilirsiniz. Hangi listenin kullanılacağını sağlayıcı belirler. Üçüncü taraf kimlik bilgisi sağlayıcılarıyla ayrıcalıklı erişim elde etmek için üçüncü tarafın sağladığı belgelere bakın.

Cihazda sağlayıcıları etkinleştirme

Kullanıcılar, sağlayıcıyı cihaz ayarları > Şifreler ve Hesaplar > Sağlayıcınız > Etkinleştir veya Devre Dışı Bırak üzerinden etkinleştirmelidir.

fun createSettingsPendingIntent(): PendingIntent