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

Credential Manager หมายถึงชุด API ที่เปิดตัวใน Android 14 ซึ่งรองรับวิธีการลงชื่อเข้าใช้หลายวิธี เช่น ผู้ใช้และรหัสผ่าน พาสคีย์ และโซลูชันการลงชื่อเข้าใช้แบบรวมศูนย์ (เช่น ฟีเจอร์ลงชื่อเข้าใช้ด้วย Google) เมื่อเรียกใช้ Credential Manager 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 ที่แสดงด้านบนเป็นส่วนสําคัญของขั้นตอน Credential Manager เพื่อให้ทํางานได้ตามที่คาดไว้ สิทธิ์นี้จําเป็นเพื่อให้มีเพียงระบบ 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. ระยะแรกคือระยะเริ่มต้น/การค้นหา ซึ่งระบบจะเชื่อมโยงกับบริการของผู้ให้บริการข้อมูลเข้าสู่ระบบและเรียกใช้เมธอด 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 ของกิจกรรมแล้ว ให้เข้าถึง Intent ที่เชื่อมโยงและส่งไปยังคลาส PendingIntentHander เพื่อรับ ProviderCreateCredentialRequest
  3. ดึง requestJson, callingAppInfo และ clientDataHash ออกจากคำขอ
  4. ดึง accountId ในพื้นที่ออกจากข้อมูลเพิ่มเติมของ Intent นี่เป็นตัวอย่างการใช้งานเฉพาะแอปและไม่จำเป็น คุณใช้รหัสบัญชีนี้เพื่อจัดเก็บข้อมูลเข้าสู่ระบบนี้กับรหัสบัญชีนี้ได้
  5. ตรวจสอบ requestJson ตัวอย่างด้านล่างใช้คลาสข้อมูลในเครื่อง เช่น PublicKeyCredentialCreationOptions เพื่อแปลง JSON อินพุตเป็นคลาสที่มีโครงสร้างตามข้อกำหนดของ WebAuthn ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถแทนที่การดำเนินการนี้ด้วยโปรแกรมแยกวิเคราะห์ของคุณเองได้
  6. ตรวจสอบลิงก์ชิ้นงานสำหรับแอปการโทรหากการโทรมาจากแอป Android ที่มาพร้อมเครื่อง
  7. แสดงข้อความแจ้งให้ตรวจสอบสิทธิ์ ตัวอย่างด้านล่างใช้ Android Biometric 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() ที่กล่าวถึงในส่วนก่อนหน้า ให้เพิ่มอีกเคสหนึ่งในบล็อกสวิตช์สำหรับการจัดการคำขอรหัสผ่าน
  • ขณะสร้าง 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 ที่เกี่ยวข้องจะดำเนินการและเรียกกิจกรรมที่เกี่ยวข้องขึ้นมา เข้าถึง Intent ที่เชื่อมโยงซึ่งส่งมาใน 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 เป็นข้อมูลเพิ่มเติมใน Intent เพื่อให้ทราบว่าพาสคีย์นั้นแมปกับข้อมูลเข้าสู่ระบบใดเมื่อผู้ใช้เลือกรายการนี้

    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 Extra ซึ่งผู้ให้บริการข้อมูลเข้าสู่ระบบสร้างขึ้นเมื่อตั้งค่า 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.
}

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

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

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

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

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

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

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

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

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

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

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

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

fun createSettingsPendingIntent(): PendingIntent