ย้ายข้อมูลจาก FIDO2 ไปยังเครื่องมือจัดการข้อมูลเข้าสู่ระบบ

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

เหตุผลที่ย้ายข้อมูลจาก FIDO2 ไปยังเครื่องมือจัดการข้อมูลเข้าสู่ระบบ

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

  • การรองรับพาสคีย์: เครื่องมือจัดการข้อมูลเข้าสู่ระบบรองรับพาสคีย์ ซึ่งเป็นกลไกการตรวจสอบสิทธิ์แบบใหม่ที่ไม่มีรหัสผ่านที่ปลอดภัยและใช้งานง่ายกว่ารหัสผ่าน
  • วิธีการลงชื่อเข้าใช้หลายบัญชี: เครื่องมือจัดการข้อมูลเข้าสู่ระบบรองรับการลงชื่อเข้าใช้หลายวิธี ซึ่งรวมถึงรหัสผ่าน พาสคีย์ และวิธีการลงชื่อเข้าใช้แบบรวมศูนย์ ซึ่งช่วยให้ผู้ใช้ตรวจสอบสิทธิ์แอปของคุณได้ง่ายขึ้นไม่ว่าจะใช้วิธีการตรวจสอบสิทธิ์ใด
  • การรองรับผู้ให้บริการข้อมูลเข้าสู่ระบบของบุคคลที่สาม: ใน Android 14 ขึ้นไป เครื่องมือจัดการข้อมูลเข้าสู่ระบบจะรองรับผู้ให้บริการข้อมูลเข้าสู่ระบบของบุคคลที่สามหลายราย ซึ่งหมายความว่าผู้ใช้จะใช้ข้อมูลเข้าสู่ระบบที่มีอยู่จากผู้ให้บริการรายอื่นเพื่อลงชื่อเข้าใช้แอปของคุณได้
  • ประสบการณ์ของผู้ใช้ที่สอดคล้องกัน: เครื่องมือจัดการข้อมูลเข้าสู่ระบบช่วยให้ผู้ใช้ได้รับประสบการณ์การตรวจสอบสิทธิ์ที่สอดคล้องกันมากขึ้นในแอปและกลไกการลงชื่อเข้าใช้ต่างๆ วิธีนี้จะช่วยให้ผู้ใช้เข้าใจและใช้ขั้นตอนการตรวจสอบสิทธิ์ของแอปได้ง่ายขึ้น

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

อัปเดตทรัพยากร Dependency

  1. อัปเดตปลั๊กอิน Kotlin ใน build.gradle ของโปรเจ็กต์เป็นเวอร์ชัน 1.8.10 ขึ้นไป

    plugins {
      //…
        id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
      //…
    }
    
  2. ใน build.gradle ของโปรเจ็กต์ ให้อัปเดต Dependency เพื่อใช้เครื่องมือจัดการข้อมูลเข้าสู่ระบบและการตรวจสอบสิทธิ์บริการ Play

    dependencies {
      // ...
      // Credential Manager:
      implementation 'androidx.credentials:credentials:<latest-version>'
    
      // Play Services Authentication:
      // Optional - needed for credentials support from play services, for devices running
      // Android 13 and below:
      implementation 'androidx.credentials:credentials-play-services-auth:<latest-version>'
      // ...
    }
    
  3. แทนที่การเริ่มต้น FIDO ด้วยการเริ่มต้นใช้งานเครื่องมือจัดการข้อมูลเข้าสู่ระบบ เพิ่มการประกาศนี้ในคลาสที่คุณใช้สำหรับการสร้างพาสคีย์และวิธีการลงชื่อเข้าใช้

    val credMan = CredentialManager.create(context)
    

สร้างพาสคีย์

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

รูปที่ 1 รูปภาพนี้แสดงวิธีแลกเปลี่ยนข้อมูลระหว่างแอปกับเซิร์ฟเวอร์เมื่อสร้างพาสคีย์โดยใช้เครื่องมือจัดการข้อมูลเข้าสู่ระบบ
  1. หากต้องการรับพารามิเตอร์ที่จำเป็นซึ่งส่งไปยังcreateCredential()วิธีname("residentKey").value("required")ระหว่างการสร้างพาสคีย์ ให้เพิ่มname("residentKey").value("required") (ตามที่อธิบายไว้ในข้อกำหนด WebAuthn) ลงในregisterRequest()การเรียกเซิร์ฟเวอร์

    suspend fun registerRequest(sessionId: String ... {
        // ...
        .method("POST", jsonRequestBody {
            name("attestation").value("none")
            name("authenticatorSelection").objectValue {
                name("residentKey").value("required")
            }
        }).build()
        // ...
    }
    
  2. ตั้งค่าประเภท return สำหรับ registerRequest() และฟังก์ชันย่อยทั้งหมดเป็น JSONObject

    suspend fun registerRequest(sessionId: String): ApiResult<JSONObject> {
        val call = client.newCall(
            Request.Builder()
                .url("$BASE_URL/<your api url>")
                .addHeader("Cookie", formatCookie(sessionId))
                .method("POST", jsonRequestBody {
                    name("attestation").value("none")
                    name("authenticatorSelection").objectValue {
                        name("authenticatorAttachment").value("platform")
                        name("userVerification").value("required")
                        name("residentKey").value("required")
                    }
                }).build()
        )
        val response = call.await()
        return response.result("Error calling the api") {
            parsePublicKeyCredentialCreationOptions(
                body ?: throw ApiException("Empty response from the api call")
            )
        }
    }
    
  3. นำวิธีการที่จัดการตัวเปิดใช้งาน Intent และการเรียกใช้ผลการค้นหากิจกรรมออกจากมุมมองของคุณอย่างปลอดภัย

  4. เนื่องจากตอนนี้ registerRequest() แสดงผลเป็น JSONObject คุณจึงไม่จำเป็นต้องสร้าง PendingIntent แทนที่ Intent ที่แสดงผลด้วย JSONObject อัปเดตการเรียกตัวเปิดใช้งาน Intent เพื่อเรียก createCredential() จาก Credential Manager API เรียกเมธอด createCredential() API

    suspend fun createPasskey(
        activity: Activity,
        requestResult: JSONObject
        ): CreatePublicKeyCredentialResponse? {
            val request = CreatePublicKeyCredentialRequest(requestResult.toString())
            var response: CreatePublicKeyCredentialResponse? = null
            try {
                response = credMan.createCredential(
                    request as CreateCredentialRequest,
                    activity
                ) as CreatePublicKeyCredentialResponse
            } catch (e: CreateCredentialException) {
    
                showErrorAlert(activity, e)
    
                return null
            }
            return response
        }
    
  5. เมื่อโทรสำเร็จแล้ว ให้ส่งการตอบกลับกลับไปที่เซิร์ฟเวอร์ คำขอและการตอบกลับสำหรับการเรียกนี้คล้ายกับการใช้งาน FIDO2 คุณจึงไม่ต้องเปลี่ยนแปลงใดๆ

ตรวจสอบสิทธิ์ด้วยพาสคีย์

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

รูปที่ 2 ขั้นตอนการตรวจสอบสิทธิ์พาสคีย์ของเครื่องมือจัดการข้อมูลเข้าสู่ระบบ
  1. การเรียกคําขอลงชื่อเข้าใช้ไปยังเซิร์ฟเวอร์เพื่อรับข้อมูลที่จําเป็นเพื่อส่งไปยังคําขอ getCredential() จะเหมือนกับการติดตั้งใช้งาน FIDO2 คุณไม่จำเป็นต้องทำการเปลี่ยนแปลงใดๆ
  2. เช่นเดียวกับการเรียกคําขอลงทะเบียน คําตอบที่แสดงจะอยู่ในรูปแบบ JSONObject

    /**
     * @param sessionId The session ID to be used for the sign-in.
     * @param credentialId The credential ID of this device.
     * @return a JSON object.
     */
    suspend fun signinRequest(): ApiResult<JSONObject> {
        val call = client.newCall(Builder().url(buildString {
            append("$BASE_URL/signinRequest")
        }).method("POST", jsonRequestBody {})
            .build()
        )
        val response = call.await()
        return response.result("Error calling /signinRequest") {
            parsePublicKeyCredentialRequestOptions(
                body ?: throw ApiException("Empty response from /signinRequest")
            )
        }
    }
    
    /**
     * @param sessionId The session ID to be used for the sign-in.
     * @param response The JSONObject for signInResponse.
     * @param credentialId id/rawId.
     * @return A list of all the credentials registered on the server,
     * including the newly-registered one.
     */
    suspend fun signinResponse(
        sessionId: String, response: JSONObject, credentialId: String
        ): ApiResult<Unit> {
    
            val call = client.newCall(
                Builder().url("$BASE_URL/signinResponse")
                    .addHeader("Cookie",formatCookie(sessionId))
                    .method("POST", jsonRequestBody {
                        name("id").value(credentialId)
                        name("type").value(PUBLIC_KEY.toString())
                        name("rawId").value(credentialId)
                        name("response").objectValue {
                            name("clientDataJSON").value(
                                response.getString("clientDataJSON")
                            )
                            name("authenticatorData").value(
                                response.getString("authenticatorData")
                            )
                            name("signature").value(
                                response.getString("signature")
                            )
                            name("userHandle").value(
                                response.getString("userHandle")
                            )
                        }
                    }).build()
            )
            val apiResponse = call.await()
            return apiResponse.result("Error calling /signingResponse") {
            }
        }
    
  3. นำวิธีการที่จัดการตัวเปิด Intent และการเรียกใช้ผลการค้นหาของกิจกรรมออกจากมุมมองของคุณอย่างปลอดภัย

  4. เนื่องจากตอนนี้ signInRequest() แสดงผล JSONObject แล้ว คุณจึงไม่จำเป็นต้องสร้าง PendingIntent แทนที่ Intent ที่แสดงผลด้วย JSONObject แล้วเรียกใช้ getCredential() จากเมธอด API

    suspend fun getPasskey(
        activity: Activity,
        creationResult: JSONObject
        ): GetCredentialResponse? {
            Toast.makeText(
                activity,
                "Fetching previously stored credentials",
                Toast.LENGTH_SHORT)
                .show()
            var result: GetCredentialResponse? = null
            try {
                val request= GetCredentialRequest(
                    listOf(
                        GetPublicKeyCredentialOption(
                            creationResult.toString(),
                            null
                        ),
                        GetPasswordOption()
                    )
                )
                result = credMan.getCredential(activity, request)
                if (result.credential is PublicKeyCredential) {
                    val publicKeycredential = result.credential as PublicKeyCredential
                    Log.i("TAG", "Passkey ${publicKeycredential.authenticationResponseJson}")
                    return result
                }
            } catch (e: Exception) {
                showErrorAlert(activity, e)
            }
            return result
        }
    
  5. เมื่อการเรียกใช้สำเร็จแล้ว ให้ส่งการตอบกลับกลับไปยังเซิร์ฟเวอร์เพื่อตรวจสอบและตรวจสอบสิทธิ์ผู้ใช้ พารามิเตอร์คำขอและการตอบกลับสำหรับการเรียก API นี้คล้ายกับการใช้งาน FIDO2 จึงไม่ต้องทำการเปลี่ยนแปลงใดๆ

แหล่งข้อมูลเพิ่มเติม