ผสานรวมเครื่องมือจัดการข้อมูลเข้าสู่ระบบกับโซลูชันของผู้ให้บริการข้อมูลเข้าสู่ระบบ

Credential Manager หมายถึงชุด API ที่เปิดตัวใน Android 14 ซึ่งรองรับวิธีการลงชื่อเข้าใช้หลายวิธี เช่น ผู้ใช้และรหัสผ่าน พาสคีย์ และโซลูชันการลงชื่อเข้าใช้แบบรวมศูนย์ (เช่น ฟีเจอร์ลงชื่อเข้าใช้ด้วย Google) เมื่อ API เครื่องมือจัดการข้อมูลเข้าสู่ระบบ มีการเรียกใช้ ระบบ Android จะรวมข้อมูลเข้าสู่ระบบจากข้อมูลเข้าสู่ระบบทั้งหมด ผู้ให้บริการที่ติดตั้งบนอุปกรณ์ เอกสารนี้อธิบายชุด API ที่ให้บริการปลายทางการผสานรวมสําหรับผู้ให้บริการข้อมูลเข้าสู่ระบบเหล่านี้

ตั้งค่า

ก่อนใช้ฟังก์ชันการทำงานในผู้ให้บริการข้อมูลเข้าสู่ระบบ ให้ทำตามขั้นตอนการตั้งค่าที่แสดงในส่วนต่อไปนี้

ประกาศทรัพยากร Dependency

ในไฟล์ build.gradle ของโมดูล ให้ประกาศทรัพยากร Dependency โดยใช้ เวอร์ชันล่าสุด เวอร์ชันของไลบรารีเครื่องมือจัดการข้อมูลเข้าสู่ระบบดังนี้

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

ประกาศองค์ประกอบบริการในไฟล์ Manifest

ในไฟล์ Manifest ของแอป AndroidManifest.xml ให้ใส่ <service> สำหรับคลาสบริการที่ขยาย CredentialProviderService จากไลบรารี androidx.credentials ดังที่แสดงในตัวอย่างด้านล่าง

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

สิทธิ์และตัวกรอง Intent ที่แสดงด้านบนเป็นส่วนสำคัญสำหรับเอกสารรับรอง ขั้นตอนของผู้จัดการเพื่อให้ทำงานได้ตามที่คาดไว้ สิทธิ์นี้จําเป็นเพื่อให้มีเพียงระบบ Android เท่านั้นที่ลิงก์กับบริการนี้ได้ ตัวกรอง Intent ใช้สำหรับการค้นพบบริการนี้ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบที่จะใช้โดยตัวจัดการข้อมูลเข้าสู่ระบบ

ประกาศประเภทข้อมูลเข้าสู่ระบบที่รองรับ

ในไดเรกทอรี res/xml ให้สร้างไฟล์ใหม่ชื่อ provider.xml ในไฟล์นี้ ให้ประกาศประเภทข้อมูลเข้าสู่ระบบที่บริการของคุณรองรับผ่านค่าคงที่ที่กําหนดไว้สําหรับข้อมูลเข้าสู่ระบบแต่ละประเภทในไลบรารี ดังต่อไปนี้ เช่น บริการรองรับรหัสผ่านแบบเดิมและพาสคีย์ ค่าคงที่ซึ่งมีความหมายว่า TYPE_PASSWORD_CREDENTIAL และ 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>

ในระดับ API ก่อนหน้านี้ ผู้ให้บริการข้อมูลเข้าสู่ระบบจะผสานรวมกับ API เช่น การป้อนข้อความอัตโนมัติ สำหรับรหัสผ่านและข้อมูลอื่นๆ ผู้ให้บริการเหล่านี้สามารถใช้ เพื่อจัดเก็บประเภทข้อมูลเข้าสู่ระบบที่มีอยู่ พร้อมกับขยายไปยัง สนับสนุนผู้ใช้คนอื่นๆ รวมถึงพาสคีย์

แนวทางแบบ 2 ระยะในการโต้ตอบกับผู้ให้บริการ

เครื่องมือจัดการข้อมูลเข้าสู่ระบบจะโต้ตอบกับผู้ให้บริการข้อมูลเข้าสู่ระบบใน 2 ระยะ ดังนี้

  1. ระยะที่ 1 คือระยะเริ่มต้น/การค้นหา ซึ่งระบบจะเชื่อมโยงกับบริการของผู้ให้บริการข้อมูลเข้าสู่ระบบและเรียกใช้เมธอด onBeginGetCredentialRequest(), onBeginCreateCredentialRequest() หรือ onClearCredentialStateRequest() ด้วยคําขอ Begin… ผู้ให้บริการต้องประมวลผลคำขอเหล่านี้และตอบกลับด้วยคําตอบ Begin… โดยป้อนข้อมูลที่มีรายการที่แสดงถึงตัวเลือกภาพที่จะแสดงในตัวเลือกบัญชี แต่ละรายการต้องมีชุด PendingIntent
  2. เมื่อผู้ใช้เลือกรายการหนึ่ง ระยะการเลือกจะเริ่มขึ้น และระบบจะเรียกใช้ PendingIntent ที่เชื่อมโยงกับรายการนั้น ซึ่งจะแสดงกิจกรรมของผู้ให้บริการที่เกี่ยวข้อง เมื่อผู้ใช้ดำเนินการเสร็จแล้ว ผู้ให้บริการข้อมูลเข้าสู่ระบบจะต้องตั้งค่าการตอบกลับผลลัพธ์ของ กิจกรรมก่อนที่จะสิ้นสุด จากนั้นระบบจะส่งการตอบกลับนี้ไปยังแอปไคลเอ็นต์ที่เรียกใช้เครื่องมือจัดการข้อมูลเข้าสู่ระบบ

จัดการการสร้างพาสคีย์

จัดการการค้นหาการสร้างพาสคีย์

เมื่อแอปไคลเอ็นต์ต้องการสร้างพาสคีย์และจัดเก็บไว้กับผู้ให้บริการข้อมูลเข้าสู่ระบบ แอปจะเรียกใช้ createCredential API วิธีจัดการกับเรื่องนี้ ในบริการของผู้ให้บริการข้อมูลเข้าสู่ระบบของคุณว่าพาสคีย์ ที่จัดเก็บไว้ในพื้นที่เก็บข้อมูล ให้ทําตามขั้นตอนที่แสดงไว้ในส่วนต่อไปนี้

  1. ลบล้างเมธอด onBeginCreateCredentialRequest() ในบริการของคุณซึ่งขยายมาจาก CredentialProviderService
  2. จัดการ BeginCreateCredentialRequest โดยสร้าง BeginCreateCredentialResponse ที่เกี่ยวข้องและส่งผ่านผ่านการเรียกกลับ
  3. ขณะที่สร้าง BeginCreateCredentialResponse ให้เพิ่ม ต้องใช้ CreateEntries CreateEntry แต่ละรายการควรสอดคล้องกับบัญชีที่บันทึกข้อมูลเข้าสู่ระบบได้ และต้องตั้งค่า PendingIntent พร้อมกับข้อมูลเมตาอื่นๆ ที่จำเป็น

ตัวอย่างต่อไปนี้จะแสดงวิธีดำเนินการตามขั้นตอนเหล่านี้

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 ควรเป็นไปตามข้อกำหนดต่อไปนี้

  • ควรตั้งค่ากิจกรรมที่เกี่ยวข้องให้แสดงขึ้นมา ต้องยืนยันหรือให้เลือกข้อมูลไบโอเมตริก
  • ข้อมูลที่จำเป็นที่ผู้ให้บริการต้องใช้เมื่อกิจกรรมที่เกี่ยวข้อง ควรเป็นค่าเพิ่มเติมเกี่ยวกับ Intent ที่ใช้ในการสร้าง PendingIntent เช่น accountId ในขั้นตอนการสร้าง
  • PendingIntent ต้องสร้างขึ้นโดยใช้ Flag PendingIntent.FLAG_MUTABLE เพื่อให้ระบบเพิ่มคำขอสุดท้ายต่อท้าย Intent Extra ได้
  • PendingIntent ต้องไม่สร้างด้วย Flag PendingIntent.FLAG_ONE_SHOT เนื่องจากผู้ใช้อาจเลือกรายการหนึ่ง ย้อนกลับ แล้วเลือกรายการนั้นอีกครั้ง ซึ่งจะทำให้ PendingIntent ทำงาน 2 ครั้ง
  • PendingIntent ของคุณต้องสร้างขึ้นด้วยรหัสคำขอที่ไม่ซ้ำกัน แต่ละรายการจะมี PendingIntent ที่เกี่ยวข้องเป็นของตนเอง

จัดการการเลือกรายการสำหรับคำขอสร้างพาสคีย์

  1. เมื่อผู้ใช้เลือก CreateEntry ที่ป้อนข้อมูลไว้ก่อนหน้านี้ ระบบจะเรียกใช้ PendingIntent ที่เกี่ยวข้องและสร้างผู้ให้บริการ Activity ที่เชื่อมโยง
  2. หลังจากเรียกเมธอด onCreate ของกิจกรรมแล้ว ให้ไปที่ ที่เกี่ยวข้อง และส่งไปยังคลาส PendingIntentHander เพื่อให้ ProviderCreateCredentialRequest
  3. ดึงข้อมูล requestJson, callingAppInfo และ clientDataHash จาก อีกครั้ง
  4. ดึง accountId ในพื้นที่ออกจากข้อมูลเพิ่มเติมของ Intent นี่เป็นตัวอย่างการใช้งานเฉพาะแอปและไม่จำเป็นต้องทำ ใช้รหัสบัญชีนี้ได้ เพื่อจัดเก็บข้อมูลเข้าสู่ระบบนี้กับรหัสบัญชีที่เฉพาะเจาะจงนี้
  5. ตรวจสอบ requestJson ตัวอย่างด้านล่างใช้คลาสข้อมูลในเครื่อง เช่น PublicKeyCredentialCreationOptions เพื่อแปลง JSON อินพุตเป็นคลาสที่มีโครงสร้างตามข้อกำหนดของ WebAuthn ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถแทนที่คลาสนี้ด้วยโปรแกรมแยกวิเคราะห์ของคุณเองได้
  6. ตรวจสอบ asset-link สําหรับแอปที่โทร หากการโทรมาจากแอป Android ดั้งเดิม
  7. แสดงข้อความแจ้งให้ตรวจสอบสิทธิ์ ตัวอย่างด้านล่างใช้ Android ข้อมูลไบโอเมตริก API
  8. เมื่อตรวจสอบสิทธิ์สำเร็จแล้ว ให้สร้างcredentialIdและคู่คีย์
  9. บันทึกคีย์ส่วนตัวในฐานข้อมูลในเครื่องโดยใช้ callingAppInfo.packageName
  10. สร้างการตอบกลับ JSON ของ Web Authentication API ที่ประกอบด้วยคีย์สาธารณะและ credentialId ตัวอย่าง ด้านล่างใช้คลาสยูทิลิตีท้องถิ่น เช่น AuthenticatorAttestationResponse และ FidoPublicKeyCredential ที่ช่วยสร้าง JSON โดยอิงตาม ดังที่ได้กล่าวไว้ ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถแทนที่คลาสเหล่านี้ได้ด้วย เป็นผู้สร้างของคุณเอง
  11. สร้าง CreatePublicKeyCredentialResponse ด้วย JSON ที่สร้างขึ้นด้านบน
  12. ตั้ง CreatePublicKeyCredentialResponse เป็นส่วนเสริมใน Intent ผ่าน PendingIntentHander.setCreateCredentialResponse() แล้วตั้งค่าดังกล่าว ความตั้งใจในผลลัพธ์ของกิจกรรม
  13. ทำกิจกรรมให้เสร็จ

ตัวอย่างโค้ดด้านล่างแสดงขั้นตอนเหล่านี้ โค้ดนี้ต้องได้รับการจัดการในคลาส Activity เมื่อมีการเรียกใช้ onCreate()

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

จัดการคำถามสำหรับคำขอสร้างรหัสผ่าน

หากต้องการจัดการการค้นหาคำขอสร้างรหัสผ่าน ให้ทำดังนี้

  • ในเมธอด processCreateCredentialRequest() ที่กล่าวถึงในส่วนก่อนหน้า ให้เพิ่มเคสอื่นในบล็อก Switch เพื่อจัดการคำขอรหัสผ่าน
  • ขณะที่สร้าง BeginCreateCredentialResponse ให้เพิ่ม CreateEntries
  • CreateEntry แต่ละรายการควรสอดคล้องกับบัญชีที่ใช้ข้อมูลเข้าสู่ระบบได้ บันทึกแล้ว และต้องตั้งค่า PendingIntent พร้อมด้วยข้อมูลเมตาอื่นๆ

ตัวอย่างต่อไปนี้จะแสดงวิธีดำเนินการตามขั้นตอนเหล่านี้

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

จัดการการเลือกรายการสำหรับคำขอสร้างรหัสผ่าน

เมื่อผู้ใช้เลือก CreateEntry ที่ป้อน พารามิเตอร์ที่เกี่ยวข้อง PendingIntent เรียกใช้และแสดงกิจกรรมที่เกี่ยวข้อง เข้าถึง ที่เกี่ยวข้องที่ส่งใน onCreate และส่งไปยัง PendingIntentHander เพื่อรับเมธอด ProviderCreateCredentialRequest

ตัวอย่างด้านล่างจะแสดงวิธีนำกระบวนการนี้ไปใช้ รหัสนี้ต้องได้รับการจัดการในเมธอด onCreate() ของกิจกรรม

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

จัดการการลงชื่อเข้าใช้ของผู้ใช้

มีการจัดการการลงชื่อเข้าใช้ของผู้ใช้ตามขั้นตอนต่อไปนี้

  • เมื่อแอปไคลเอ็นต์พยายามลงชื่อเข้าใช้ผู้ใช้ แอปจะเตรียม GetCredentialRequest
  • เฟรมเวิร์ก Android เผยแพร่คำขอนี้ไปยังข้อมูลเข้าสู่ระบบที่เกี่ยวข้องทั้งหมด ผู้ให้บริการได้โดยเชื่อมโยงกับบริการเหล่านี้
  • ผู้ให้บริการจะได้รับ BeginGetCredentialRequest ที่มี รายการ BeginGetCredentialOption แต่ละรายการมีพารามิเตอร์ที่ สามารถใช้เพื่อเรียกข้อมูลเข้าสู่ระบบที่ตรงกันได้

หากต้องการจัดการคำขอนี้ในบริการของผู้ให้บริการข้อมูลเข้าสู่ระบบ ให้ทำตามขั้นตอน ขั้นตอนต่อไปนี้

  1. ลบล้างเมธอด onBeginGetCredentialRequest() เพื่อจัดการคำขอ โปรดทราบว่าหากข้อมูลเข้าสู่ระบบของคุณถูกล็อก คุณสามารถตั้งค่า AuthenticationAction ในการตอบกลับและเรียกใช้ 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())
        }
    }
    

    ผู้ให้บริการที่ต้องมีการปลดล็อกข้อมูลเข้าสู่ระบบก่อนส่งคืน credentialEntries รายการใดก็ได้ต้องตั้งค่า Intent ที่รอดำเนินการซึ่งไปยังส่วนต่างๆ ให้ผู้ใช้เข้าสู่ขั้นตอนการปลดล็อกแอป ดังนี้

    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. เรียกดูข้อมูลเข้าสู่ระบบจากฐานข้อมูลในเครื่องและตั้งค่าโดยใช้ CredentialEntries เพื่อแสดงบนตัวเลือก สำหรับพาสคีย์ คุณสามารถตั้ง credentialId เป็นส่วนเพิ่มเติมของความตั้งใจเพื่อให้รู้ว่าข้อมูลเข้าสู่ระบบใด จะจับคู่เมื่อผู้ใช้เลือกรายการนี้

    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. ค้นหาข้อมูลเข้าสู่ระบบจากฐานข้อมูล สร้างรายการพาสคีย์และรหัสผ่านเพื่อป้อนข้อมูล

    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. เมื่อคุณค้นหาและเติมข้อมูลเข้าสู่ระบบแล้ว ทีนี้คุณจะต้องจัดการ การเลือกข้อมูลเข้าสู่ระบบที่ผู้ใช้เลือก ไม่ว่าจะเป็น เป็นพาสคีย์หรือรหัสผ่าน

การจัดการการเลือกพาสคีย์ของผู้ใช้

  1. ในเมธอด onCreate ของกิจกรรมที่เกี่ยวข้อง ให้ดึงข้อมูล Intent ที่เชื่อมโยง และส่งไปยัง PendingIntentHandler.retrieveProviderGetCredentialRequest()
  2. ดึงข้อมูล GetPublicKeyCredentialOption จากคำขอที่ดึงข้อมูลไว้ด้านบน จากนั้นดึงข้อมูล requestJson และ clientDataHash จากตัวเลือกนี้
  3. ดึงข้อมูล credentialId จาก Intent เพิ่มเติมซึ่งมีการเติมข้อมูลโดย เมื่อมีการตั้งค่า PendingIntent ที่เกี่ยวข้อง
  4. ดึงข้อมูลพาสคีย์จากฐานข้อมูลในเครื่องโดยใช้พารามิเตอร์คำขอที่เข้าถึงได้ด้านบน
  5. ยืนยันว่าพาสคีย์ถูกต้องด้วยข้อมูลเมตาที่ดึงมาและการยืนยันผู้ใช้

    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. หากต้องการตรวจสอบผู้ใช้ ให้แสดงข้อความแจ้งเกี่ยวกับไบโอเมตริก (หรือการยืนยันอื่นๆ ) ข้อมูลโค้ดด้านล่างใช้ Android Biometric API

  7. เมื่อการตรวจสอบสิทธิ์สําเร็จแล้ว ให้สร้างการตอบกลับ JSON ตามข้อกําหนด W3 เกี่ยวกับการยืนยันผ่านเว็บ ในตัวข้อมูลโค้ดด้านล่างนี้ คลาสข้อมูลตัวช่วย เช่น AuthenticatorAssertionResponse จะใช้เพื่อรับพารามิเตอร์ที่มีโครงสร้างและแปลงเป็นรูปแบบ JSON ที่จําเป็น คำตอบจะมีลายเซ็นดิจิทัลจาก คีย์ส่วนตัวของข้อมูลเข้าสู่ระบบ WebAuthn เซิร์ฟเวอร์ของฝ่ายที่เกี่ยวข้องยืนยันได้ ลายเซ็นนี้เพื่อตรวจสอบสิทธิ์ผู้ใช้ก่อนลงชื่อเข้าใช้

  8. สร้าง PublicKeyCredential โดยใช้ JSON ที่สร้างขึ้นด้านบน และตั้งค่าใน GetCredentialResponse สุดท้าย ตั้งค่าคำตอบสุดท้ายนี้เกี่ยวกับผลลัพธ์ของกิจกรรมนี้

ตัวอย่างต่อไปนี้แสดงวิธีใช้ขั้นตอนเหล่านี้

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)

การจัดการการเลือกผู้ใช้สำหรับการตรวจสอบสิทธิ์ด้วยรหัสผ่าน

  1. ในกิจกรรมที่เกี่ยวข้อง ให้เข้าถึง Intent ที่ส่งผ่านไปยัง onCreate และแตกข้อมูล ProviderGetCredentialRequest โดยใช้ PendingIntentHandler
  2. ใช้ GetPasswordOption ในคำขอเพื่อเรียกดูรหัสผ่าน ข้อมูลเข้าสู่ระบบสำหรับชื่อแพ็กเกจขาเข้า

    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. เมื่อดึงข้อมูลแล้ว ให้ตั้งค่าการตอบกลับสำหรับข้อมูลเข้าสู่ระบบรหัสผ่านที่เลือก

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

จัดการการเลือกรายการการดำเนินการตรวจสอบสิทธิ์

ตามที่กล่าวไว้ก่อนหน้านี้ ผู้ให้บริการข้อมูลเข้าสู่ระบบสามารถตั้งค่า AuthenticationAction หากข้อมูลเข้าสู่ระบบล็อกอยู่ หากผู้ใช้เลือกตัวเลือกนี้ ซึ่งเป็นกิจกรรมที่ตรงกับชุดการดำเนินการผ่าน Intent ใน มีการเรียกใช้ PendingIntent จากนั้นผู้ให้บริการข้อมูลเข้าสู่ระบบจะแสดงข้อมูลไบโอเมตริก ขั้นตอนการตรวจสอบสิทธิ์หรือกลไกที่คล้ายกันในการปลดล็อกข้อมูลเข้าสู่ระบบ หากดำเนินการสำเร็จ ผู้ให้บริการข้อมูลเข้าสู่ระบบต้องสร้าง BeginGetCredentialResponse ซึ่งคล้ายกับวิธีจัดการการลงชื่อเข้าใช้ของผู้ใช้ตามที่อธิบายไว้ข้างต้น เนื่องจากตอนนี้ข้อมูลเข้าสู่ระบบได้ปลดล็อกแล้ว จากนั้นต้องตั้งค่าการตอบกลับนี้ผ่านเมธอด PendingIntentHandler.setBeginGetCredentialResponse() ก่อนจึงจะตั้งค่า Intent ที่เตรียมไว้เป็นผลการค้นหาและกิจกรรมจะเสร็จสมบูรณ์

ล้างคําขอข้อมูลเข้าสู่ระบบ

แอปไคลเอ็นต์อาจขอให้ล้างสถานะที่เก็บไว้สำหรับการเลือกข้อมูลเข้าสู่ระบบ เช่น ผู้ให้บริการข้อมูลเข้าสู่ระบบอาจจำข้อมูลเข้าสู่ระบบที่เลือกไว้ก่อนหน้านี้และแสดงข้อมูลเข้าสู่ระบบนั้นครั้งถัดไปเท่านั้น แอปไคลเอ็นต์เรียกใช้ API นี้และคาดหวังว่าระบบจะล้างการเลือกที่ติดอยู่ บริการผู้ให้บริการข้อมูลเข้าสู่ระบบของคุณสามารถจัดการคําขอนี้ได้ด้วยการลบล้างวิธี onClearCredentialStateRequest() ดังนี้

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

หากต้องการอนุญาตให้ผู้ใช้เปิดการตั้งค่าของผู้ให้บริการจากรหัสผ่าน พาสคีย์ และ ป้อนข้อความอัตโนมัติ แอปของผู้ให้บริการข้อมูลเข้าสู่ระบบควรใช้ credential-providerแอตทริบิวต์ไฟล์ Manifest settingsActivityใน res/xml/provider.xml แอตทริบิวต์นี้ช่วยให้คุณใช้ Intent เพื่อเปิดแอปได้ หน้าจอการตั้งค่าของตนเอง หากผู้ใช้คลิกที่ชื่อผู้ให้บริการในรหัสผ่าน พาสคีย์ และ รายการบริการป้อนข้อความอัตโนมัติ ตั้งค่าของแอตทริบิวต์นี้เป็นชื่อของกิจกรรมที่จะเปิดจากหน้าจอการตั้งค่า

<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>
แผนภาพแสดงการเปลี่ยนแปลงและฟังก์ชันของปุ่มเปิด
รูปที่ 1: ปุ่มเปลี่ยนจะเปิดกล่องโต้ตอบการเลือกที่มีอยู่ ซึ่งช่วยให้ผู้ใช้เลือกผู้ให้บริการข้อมูลเข้าสู่ระบบที่ต้องการได้ ปุ่มเปิดจะเปิดกิจกรรมการตั้งค่าที่ระบุไว้ในการเปลี่ยนแปลงไฟล์ Manifest และเปิดหน้าการตั้งค่าสำหรับผู้ให้บริการรายนั้นโดยเฉพาะ

ความตั้งใจในการตั้งค่า

เปิดการตั้งค่า: android.settings.CREDENTIAL_PROVIDER Intent จะแสดงหน้าจอการตั้งค่าซึ่งผู้ใช้สามารถเลือก ผู้ให้บริการข้อมูลเข้าสู่ระบบเพิ่มเติม

หน้าจอการตั้งค่ารหัสผ่าน พาสคีย์ และการป้อนข้อความอัตโนมัติ
รูปที่ 2: หน้าจอการตั้งค่ารหัสผ่าน พาสคีย์ และการป้อนข้อความอัตโนมัติ

บริการข้อมูลเข้าสู่ระบบที่ต้องการ: Intent ACTION_REQUEST_SET_AUTOFILL_SERVICE จะเปลี่ยนเส้นทางผู้ใช้ไปยังหน้าจอการเลือกผู้ให้บริการที่ต้องการ ผู้ให้บริการที่เลือกในหน้าจอนี้ จะเป็นผู้ให้บริการป้อนข้อความอัตโนมัติและข้อมูลรับรองที่ต้องการ

แผนภาพแสดงฟังก์ชันปุ่มเปลี่ยนและเปิด
รูปที่ 3: บริการที่ต้องการสำหรับรหัสผ่าน พาสคีย์ หน้าจอการตั้งค่าการป้อนข้อความอัตโนมัติ

รับรายการที่อนุญาตของแอปที่มีสิทธิ์

แอปที่ได้รับสิทธิ์ เช่น เว็บเบราว์เซอร์ เรียกใช้เครื่องมือจัดการข้อมูลเข้าสู่ระบบในนามของ พาร์ทเนอร์รายอื่นที่พึ่งพาโดยการตั้งค่าพารามิเตอร์ origin ในข้อมูลเข้าสู่ระบบ ผู้จัดการ GetCredentialRequest() และ CreatePublicKeyCredentialRequest() วิธี ผู้ให้บริการข้อมูลเข้าสู่ระบบจะดึงข้อมูล origin โดยใช้ getOrigin() API เพื่อประมวลผลคําขอเหล่านี้

หากต้องการเรียกข้อมูล origin แอปของผู้ให้บริการข้อมูลเข้าสู่ระบบจะต้องส่งอยู่ในรายการ ผู้โทรที่ได้รับสิทธิ์และไว้วางใจได้ไปยัง API androidx.credentials.provider.CallingAppInfo's getOrigin() รายการที่อนุญาตนี้ ต้องเป็นออบเจ็กต์ JSON ที่ถูกต้อง ระบบจะแสดงผล origin หาก packageName และลายนิ้วมือใบรับรองที่ได้รับจาก signingInfo ตรงกับลายนิ้วมือของแอปที่พบใน privilegedAllowlist ที่ส่งไปยัง getOrigin() API หลังจาก ได้รับค่า origin แล้ว แอปผู้ให้บริการควรถือว่านี่เป็นสิทธิ์พิเศษ โทรและตั้งค่า origin นี้ในข้อมูลไคลเอ็นต์ ใน AuthenticatorResponse แทนการคำนวณ origin โดยใช้ลายเซ็นของแอปการโทร

หากคุณเรียกข้อมูล origin ให้ใช้ clientDataHash ที่ให้ไว้โดยตรง ใน CreatePublicKeyCredentialRequest() หรือ GetPublicKeyCredentialOption() แทนการประกอบและแฮช clientDataJSON ระหว่างคำขอลงนาม หากต้องการหลีกเลี่ยงปัญหาการแยกวิเคราะห์ JSON ให้ตั้งค่า ค่าตัวยึดตำแหน่งสำหรับ clientDataJSON ในเอกสารรับรองและการยืนยัน คำตอบ เครื่องมือจัดการรหัสผ่านบน Google ใช้รายการที่อนุญาตแบบเปิดกว้างสำหรับ โทรหา getOrigin() ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถใช้รายการนี้หรือ ระบุของคุณเองในรูปแบบ JSON ที่อธิบายโดย API ผู้ให้บริการจะเลือกรายการที่จะใช้ หากต้องการรับสิทธิ์เข้าถึงที่มีสิทธิ์ด้วยผู้ให้บริการข้อมูลเข้าสู่ระบบบุคคลที่สาม โปรดดูเอกสารประกอบที่บุคคลที่สามให้ไว้

เปิดใช้ผู้ให้บริการในอุปกรณ์

ผู้ใช้ต้องเปิดใช้ผู้ให้บริการผ่านการตั้งค่าอุปกรณ์ > รหัสผ่านและบัญชี > ผู้ให้บริการของคุณ > เปิดหรือปิดใช้

fun createSettingsPendingIntent(): PendingIntent