Tích hợp Trình quản lý thông tin xác thực với giải pháp của trình cung cấp thông tin xác thực

Trình quản lý thông tin xác thực là một tập hợp API ra mắt trong Android 14, hỗ trợ nhiều phương thức đăng nhập, chẳng hạn như tên người dùng – mật khẩu, khoá truy cập và các giải pháp đăng nhập liên kết (ví dụ như Đăng nhập bằng Google). Khi API Trình quản lý thông tin xác thực được gọi, hệ thống Android sẽ tổng hợp thông tin xác thực từ tất cả các trình cung cấp thông tin xác thực đã cài đặt trên thiết bị. Tài liệu này mô tả tập hợp các API cung cấp điểm cuối tích hợp cho các trình cung cấp thông tin xác thực này.

Thiết lập

Trước khi triển khai chức năng trong trình cung cấp thông tin xác thực, hãy hoàn tất các bước thiết lập có ở những phần sau.

Khai báo phần phụ thuộc

Trong tệp build.gradle của mô-đun, hãy khai báo phần phụ thuộc bằng cách sử dụng phiên bản mới nhất của thư viện Trình quản lý thông tin xác thực:

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

Khai báo phần tử dịch vụ trong tệp kê khai

Trong tệp kê khai AndroidManifest.xml của ứng dụng, hãy đưa vào một phần kê khai <service> cho lớp dịch vụ mở rộng lớp CredentialProviderService của thư viện androidx.credentials, như trong ví dụ dưới đây.

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

Quyền và bộ lọc ý định trình bày ở trên là phần không thể thiếu để quy trình của Trình quản lý thông tin xác thực hoạt động như mong đợi. Quyền đó là cần thiết để chỉ hệ thống Android mới có thể liên kết với dịch vụ này. Bộ lọc ý định đó dùng để phát hiện dịch vụ này với vai trò là trình cung cấp thông tin xác thực được Trình quản lý thông tin xác thực sử dụng.

Khai báo loại thông tin đăng nhập được hỗ trợ

Trong thư mục res/xml, hãy tạo một tệp mới có tên là provider.xml. Trong tệp này, hãy khai báo các loại thông tin đăng nhập mà dịch vụ của bạn hỗ trợ, thông qua các hằng số được xác định cho từng loại thông tin đăng nhập trong thư viện. Trong ví dụ sau, dịch vụ hỗ trợ mật khẩu truyền thống cũng như khoá truy cập, các hằng số được xác định là TYPE_PASSWORD_CREDENTIALTYPE_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>

Ở những cấp độ API trước, trình cung cấp thông tin xác thực tích hợp với các API, chẳng hạn như API tự động điền đối với mật khẩu và dữ liệu khác. Các trình cung cấp này có thể sử dụng cùng một cơ sở hạ tầng nội bộ để lưu trữ những loại thông tin đăng nhập hiện có, đồng thời mở rộng cơ sở hạ tầng đó để hỗ trợ các loại thông tin đăng nhập khác, bao gồm cả khoá truy cập.

Cách tiếp cận 2 giai đoạn đối với hoạt động tương tác của trình cung cấp

Trình quản lý thông tin xác thực tương tác với trình cung cấp thông tin xác thực theo 2 giai đoạn:

  1. Giai đoạn đầu là giai đoạn bắt đầu/truy vấn, theo đó hệ thống sẽ liên kết với các dịch vụ của trình cung cấp thông tin xác thực và gọi phương thức onBeginGetCredentialRequest(), onBeginCreateCredentialRequest() hoặc onClearCredentialStateRequest() bằng các yêu cầu Begin…. Trình cung cấp phải xử lý các yêu cầu này và phản hồi bằng các phản hồi Begin…. Đây là những phản hồi được điền sẵn bằng các mục nhập biểu thị cho các tuỳ chọn trực quan xuất hiện trên bộ chọn tài khoản. Bạn phải đặt PendingIntent cho mỗi mục nhập.
  2. Sau khi người dùng chọn một mục nhập, giai đoạn lựa chọn sẽ bắt đầu và PendingIntent liên kết với mục nhập đó được kích hoạt, do đó, hoạt động của trình cung cấp tương ứng sẽ hiện lên. Sau khi người dùng tương tác xong với hoạt động này, trình cung cấp thông tin xác thực phải đặt phản hồi cho kết quả của hoạt động trước khi kết thúc. Sau đó, phản hồi này được gửi tới ứng dụng khách đã gọi Trình quản lý thông tin xác thực.

Xử lý việc tạo khoá truy cập

Xử lý truy vấn tạo khoá truy cập

Khi một ứng dụng khách muốn tạo khoá truy cập và lưu trữ khoá đó bằng trình cung cấp thông tin xác thực, ứng dụng khách đó sẽ gọi API createCredential. Để xử lý yêu cầu này trong dịch vụ của trình cung cấp thông tin xác thực sao cho khoá truy cập thực sự được lưu trữ trong bộ nhớ, hãy hoàn tất các bước ở những phần sau.

  1. Ghi đè phương thức onBeginCreateCredentialRequest() trong dịch vụ của bạn được mở rộng từ CredentialProviderService.
  2. Xử lý BeginCreateCredentialRequest bằng cách tạo BeginCreateCredentialResponse tương ứng và truyền mã này qua lệnh gọi lại.
  3. Trong khi tạo BeginCreateCredentialResponse, hãy thêm CreateEntries bắt buộc. Mỗi CreateEntry phải tương ứng với một tài khoản có thể lưu thông tin xác thực và phải đặt PendingIntent cùng với siêu dữ liệu bắt buộc khác.

Ví dụ sau minh hoạ cách triển khai các bước này.

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

Trong quá trình tạo PendingIntent, bạn cần tuân thủ những điều sau:

  • Nên thiết lập Hoạt động tương ứng để hiện mọi lời nhắc bắt buộc về Sinh trắc học, thông tin xác nhận hoặc lựa chọn cần thiết.
  • Nên đặt mọi dữ liệu bắt buộc (mà trình cung cấp cần đến khi hoạt động tương ứng được gọi) làm ý định bổ sung dùng để tạo PendingIntent, chẳng hạn như accountId trong quy trình tạo.
  • Phải tạo PendingIntent bằng cờ PendingIntent.FLAG_MUTABLE để hệ thống có thể thêm yêu cầu cuối cùng vào ý định bổ sung.
  • Không được tạo PendingIntent bằng cờ PendingIntent.FLAG_ONE_SHOT vì người dùng có thể chọn một mục nhập, quay lại và chọn lại mục nhập này. Điều này sẽ khiến PendingIntent kích hoạt 2 lần.
  • Phải tạo PendingIntent bằng một mã yêu cầu duy nhất để mỗi mục nhập đều có thể có PendingIntent tương ứng.

Xử lý lựa chọn mục nhập cho các yêu cầu tạo khoá truy cập

  1. Khi người dùng chọn một CreateEntry đã điền trước đây, PendingIntent tương ứng sẽ được gọi và Activity của trình cung cấp liên kết sẽ được tạo.
  2. Sau khi phương thức onCreate của Hoạt động được gọi, hãy truy cập ý định liên kết và truyền ý định đó vào lớp PendingIntentHander để lấy ProviderCreateCredentialRequest.
  3. Trích xuất requestJson, callingAppInfoclientDataHash từ yêu cầu.
  4. Trích xuất accountId cục bộ từ ý định bổ sung. Đây là cách triển khai dành riêng cho ứng dụng mẫu và không bắt buộc. Mã tài khoản này có thể được dùng để lưu trữ thông tin xác thực này theo mã tài khoản cụ thể này.
  5. Xác thực requestJson. Ví dụ bên dưới sử dụng các lớp dữ liệu cục bộ như PublicKeyCredentialCreationOptions để chuyển đổi JSON đầu vào thành một lớp có cấu trúc theo thông số WebAuthn. Đối với trình cung cấp thông tin xác thực, bạn có thể thay thế thông số này bằng trình phân tích cú pháp riêng.
  6. Kiểm tra asset-link của ứng dụng gọi nếu lệnh gọi bắt nguồn từ một ứng dụng Android gốc.
  7. Hiện lời nhắc xác thực. Ví dụ bên dưới sử dụng API sinh trắc học của Android.
  8. Khi xác thực thành công, hãy tạo một credentialId và một cặp khoá.
  9. Lưu khoá riêng tư trong cơ sở dữ liệu cục bộ của bạn theo callingAppInfo.packageName.
  10. Tạo một phản hồi JSON cho API xác thực web bao gồm khoá công khaicredentialId. Ví dụ bên dưới sử dụng các lớp tiện ích cục bộ như AuthenticatorAttestationResponseFidoPublicKeyCredential. Các lớp này giúp tạo JSON dựa trên thông số kỹ thuật đã đề cập trước đó. Là nhà cung cấp thông tin xác thực, bạn có thể thay thế các lớp này bằng trình tạo của riêng mình.
  11. Tạo CreatePublicKeyCredentialResponse bằng JSON được tạo ở trên.
  12. Đặt CreatePublicKeyCredentialResponse làm ý định bổ sung trên Intent thông qua PendingIntentHander.setCreateCredentialResponse() và đặt ý định đó thành kết quả của Hoạt động.
  13. Hoàn tất Hoạt động.

Ví dụ về mã dưới đây minh hoạ các bước này. Mã này cần được xử lý trong lớp Hoạt động sau khi onCreate() được gọi.

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

Xử lý các truy vấn về yêu cầu tạo mật khẩu

Để xử lý các truy vấn về yêu cầu tạo mật khẩu, hãy làm như sau:

  • Bên trong phương thức processCreateCredentialRequest() đề cập ở phần trước, hãy thêm một trường hợp khác vào khối chuyển đổi để xử lý các yêu cầu về mật khẩu.
  • Trong khi tạo BeginCreateCredentialResponse, hãy thêm CreateEntries bắt buộc.
  • Mỗi CreateEntry cần phải tương ứng với một tài khoản có thể lưu thông tin xác thực và phải đặt một PendingIntent trong đó cùng với các siêu dữ liệu khác.

Ví dụ sau đây minh hoạ cách triển khai các bước này:

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

Xử lý lựa chọn mục nhập cho các yêu cầu tạo mật khẩu

Khi người dùng chọn một CreateEntry được điền sẵn, PendingIntent tương ứng sẽ thực thi và cho thấy Hoạt động được liên kết. Hãy truy cập ý định liên kết được truyền vào onCreate rồi truyền ý định đó vào lớp PendingIntentHander để lấy phương thức ProviderCreateCredentialRequest.

Ví dụ dưới đây minh hoạ cách triển khai quy trình này. Mã này cần được xử lý trong phương thức onCreate() của Hoạt động.

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

Xử lý quy trình đăng nhập của người dùng

Quy trình đăng nhập của người dùng được xử lý theo các bước sau:

  • Khi tìm cách đăng nhập cho người dùng, ứng dụng khách sẽ chuẩn bị một thực thể GetCredentialRequest.
  • Khung Android truyền yêu cầu này đến tất cả các trình cung cấp thông tin xác thực hiện có bằng cách liên kết với các dịch vụ này.
  • Sau đó, dịch vụ của trình cung cấp sẽ nhận được BeginGetCredentialRequest chứa danh sách BeginGetCredentialOption. Trong đó mỗi danh sách chứa các tham số có thể dùng để truy xuất thông tin xác thực trùng khớp.

Để xử lý yêu cầu này trong dịch vụ của trình cung cấp thông tin xác thực, hãy hoàn tất các bước sau:

  1. Ghi đè phương thức onBeginGetCredentialRequest() để xử lý yêu cầu. Lưu ý rằng nếu thông tin xác thực của bạn bị khoá, thì bạn có thể đặt AuthenticationAction ngay trong phản hồi và thực hiện lệnh gọi lại.

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

    Các trình cung cấp (yêu cầu mở khoá thông tin xác thực trước khi trả về bất kỳ credentialEntries nào) phải thiết lập một ý định đang chờ xử lý để chuyển người dùng đến quy trình mở khoá của ứng dụng:

    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. Truy xuất thông tin xác thực từ cơ sở dữ liệu cục bộ và thiết lập bằng cách sử dụng CredentialEntries để được xuất hiện trong bộ chọn. Đối với khoá truy cập, bạn có thể đặt credentialId làm ý định bổ sung để biết ý định đó liên kết với thông tin xác thực nào khi người dùng chọn mục nhập này.

    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)
    }
    
  3. Truy vấn thông tin xác thực từ cơ sở dữ liệu, tạo khoá truy cập và mục nhập mật khẩu cần điền.

    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)
        )
    }
    
  4. Sau khi truy vấn và điền thông tin xác thực, giờ đây, bạn cần phải xử lý giai đoạn lựa chọn thông tin xác thực mà người dùng đã chọn, cho dù đó là khoá truy cập hay mật khẩu.

Xử lý lựa chọn của người dùng về khoá truy cập

  1. Trong phương thức onCreate của Hoạt động tương ứng, hãy truy xuất ý định liên kết rồi chuyển vào PendingIntentHandler.retrieveProviderGetCredentialRequest().
  2. Trích xuất GetPublicKeyCredentialOption từ yêu cầu đã truy xuất ở trên. Sau đó, hãy trích xuất requestJsonclientDataHash từ tuỳ chọn này.
  3. Trích xuất credentialId từ ý định bổ sung, do trình cung cấp thông tin xác thực điền vào khi PendingIntent tương ứng được thiết lập.
  4. Trích xuất khoá truy cập từ cơ sở dữ liệu cục bộ của bạn bằng cách sử dụng các tham số yêu cầu đã truy cập ở trên.
  5. Hãy xác nhận rằng khoá truy cập phù hợp với siêu dữ liệu được trích xuất và quy trình xác minh người dùng.

    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
    )
    
  6. Để xác thực người dùng, hãy hiện lời nhắc về Sinh trắc học (hoặc một phương thức xác nhận khác). Đoạn mã dưới đây sử dụng API sinh trắc học của Android.

  7. Sau khi xác thực thành công, hãy tạo phản hồi JSON dựa trên thông số Xác thực web của W3. Trong đoạn mã dưới đây, các lớp dữ liệu của trình trợ giúp như AuthenticatorAssertionResponse được dùng để lấy các tham số có cấu trúc và chuyển đổi chúng thành định dạng JSON bắt buộc. Phản hồi này chứa chữ ký số trong khoá riêng tư của thông tin xác thực WebAuthn. Máy chủ của bên phụ thuộc có thể xác minh chữ ký này để xác thực người dùng trước khi đăng nhập.

  8. Tạo PublicKeyCredential bằng cách sử dụng JSON được tạo ở trên và đặt mã này vào GetCredentialResponse cuối cùng. Đặt phản hồi cuối cùng này vào kết quả của hoạt động này.

Ví dụ sau đây minh hoạ cách triển khai các bước này:

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)

Xử lý lựa chọn của người dùng về quy trình xác thực mật khẩu

  1. Trong hoạt động tương ứng, hãy truy cập ý định được truyền vào onCreate và trích xuất ProviderGetCredentialRequest bằng PendingIntentHandler.
  2. Sử dụng GetPasswordOption trong yêu cầu để truy xuất thông tin xác thực mật khẩu cho tên gói gửi đến.

    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
        }
    }
    
  3. Sau khi truy xuất, hãy đặt phản hồi cho thông tin xác thực mật khẩu đã chọn.

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

Xử lý lựa chọn về mục nhập hành động xác thực

Như đã đề cập trước đó, trình cung cấp thông tin xác thực có thể đặt AuthenticationAction nếu thông tin xác thực bị khoá. Nếu người dùng chọn mục nhập này, Hoạt động tương ứng với thao tác theo ý định được đặt trong PendingIntent sẽ được gọi. Sau đó, trình cung cấp thông tin xác thực có thể hiển thị quy trình xác thực bằng sinh trắc học hoặc cơ chế tương tự để mở khoá thông tin xác thực. Khi thành công, trình cung cấp thông tin xác thực phải tạo BeginGetCredentialResponse, tương tự như cách xử lý đăng nhập của người dùng được mô tả ở trên, vì thông tin xác thực hiện đã được mở khoá. Sau đó, phản hồi này phải được đặt thông qua phương thức PendingIntentHandler.setBeginGetCredentialResponse() trước khi ý định theo dự tính được đặt làm kết quả và Hoạt động kết thúc.

Xoá yêu cầu về thông tin xác thực

Một ứng dụng khách có thể yêu cầu xoá mọi trạng thái được duy trì để chọn thông tin xác thực, chẳng hạn như trình cung cấp thông tin xác thực có thể ghi nhớ thông tin xác thực đã chọn trước đó và chỉ trả về thông tin xác thực đó vào lần tiếp theo. Một ứng dụng khách sẽ gọi API này và muốn xoá lựa chọn cố định. Dịch vụ của trình cung cấp thông tin xác thực của bạn có thể xử lý yêu cầu này bằng cách ghi đè phương thức onClearCredentialStateRequest():

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

Để cho phép người dùng mở phần cài đặt của trình cung cấp từ màn hình Mật khẩu, khoá truy cập và tự động điền, ứng dụng trình cung cấp thông tin xác thực phải triển khai thuộc tính tệp kê khai credential-provider settingsActivity trong res/xml/provider.xml. Thuộc tính này cho phép bạn dùng ý định để mở màn hình cài đặt riêng của ứng dụng nếu người dùng nhấp vào tên nhà cung cấp trong danh sách dịch vụ Mật khẩu, khoá truy cập và tự động điền. Đặt giá trị của thuộc tính này thành tên của hoạt động sẽ được chạy từ màn hình cài đặt.

<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>
Sơ đồ thể hiện các chức năng của nút thay đổi và mở
Hình 1: Nút Thay đổi sẽ mở hộp thoại lựa chọn hiện có, cho phép người dùng chọn trình cung cấp thông tin xác thực mà họ muốn. Nút Mở sẽ khởi chạy hoạt động cài đặt được xác định trong thay đổi tệp kê khai và mở một trang cài đặt dành riêng cho nhà cung cấp đó.

Ý định cài đặt

Mở phần cài đặt: Ý định android.settings.CREDENTIAL_PROVIDER sẽ hiện một màn hình cài đặt, trong đó người dùng có thể chọn trình cung cấp thông tin xác thực ưu tiên và trình cung cấp thông tin xác thực bổ sung.

Màn hình cài đặt Mật khẩu, khoá truy cập và tính năng tự động điền
Hình 2: Màn hình cài đặt Mật khẩu, mã xác thực và tính năng tự động điền.

Dịch vụ thông tin xác thực được ưu tiên: Ý định ACTION_REQUEST_SET_AUTOFILL_SERVICE chuyển hướng người dùng đến màn hình lựa chọn nhà cung cấp được ưu tiên. Trình cung cấp được chọn trên màn hình này sẽ trở thành thông tin xác thực và trình cung cấp dịch vụ tự động điền ưu tiên.

Sơ đồ cho thấy các hàm nút thay đổi và mở
Hình 3: Màn hình cài đặt Dịch vụ ưu tiên cho mật khẩu, khoá truy cập và tính năng tự động điền.

Nhận danh sách cho phép đối với ứng dụng có đặc quyền

Các ứng dụng có đặc quyền như trình duyệt web thực hiện lệnh gọi qua Trình quản lý thông tin xác thực thay cho các bên đáng tin cậy khác bằng cách đặt tham số origin trong Trình quản lý thông tin xác thực GetCredentialRequest() và phương thức CreatePublicKeyCredentialRequest(). Để xử lý những yêu cầu này, trình cung cấp thông tin xác thực sẽ truy xuất origin bằng cách sử dụng API getOrigin().

Để truy xuất origin, ứng dụng của nhà cung cấp thông tin xác thực cần truyền danh sách phương thức gọi có đặc quyền và đáng tin cậy đến API androidx.credentials.provider.CallingAppInfo's getOrigin(). Danh sách cho phép này phải là một đối tượng JSON hợp lệ. origin được trả về nếu packageName và dấu vân tay chứng chỉ thu được từ signingInfo khớp với dấu vân tay của một ứng dụng tìm thấy trong privilegedAllowlist được truyền đến API getOrigin(). Sau khi nhận được giá trị origin, ứng dụng của nhà cung cấp sẽ coi đây là lệnh gọi đặc quyền và đặt origin này trên dữ liệu ứng dụng trong AuthenticatorResponse, thay vì tính toán origin bằng chữ ký của ứng dụng gọi.

Nếu bạn truy xuất origin, hãy sử dụng clientDataHash được cung cấp trực tiếp trong CreatePublicKeyCredentialRequest() hoặc GetPublicKeyCredentialOption() thay vì tập hợp và băm clientDataJSON trong quá trình yêu cầu chữ ký. Để tránh gặp vấn đề khi phân tích cú pháp JSON, hãy đặt giá trị phần giữ chỗ cho clientDataJSON trong phản hồi chứng thực và xác nhận. Trình quản lý mật khẩu của Google sử dụng một danh sách cho phép có sẵn công khai cho các lệnh gọi đến getOrigin(). Là nhà cung cấp thông tin xác thực, có thể bạn phải sử dụng danh sách này hoặc cung cấp danh sách riêng ở định dạng JSON mà API mô tả. Nhà cung cấp có quyền chọn danh sách nào được sử dụng. Để có quyền truy cập đặc quyền với nhà cung cấp thông tin xác thực bên thứ ba, hãy tham khảo tài liệu do bên thứ ba cung cấp.

Bật trình cung cấp trên thiết bị

Người dùng phải bật trình cung cấp thông qua phần Cài đặt thiết bị > Mật khẩu và tài khoản > Trình cung cấp của bạn > Bật hoặc tắt.

fun createSettingsPendingIntent(): PendingIntent