การแก้ไขครั้งที่ 4 ปี 2025: ดูวิธีลดความซับซ้อนของเส้นทางการให้สิทธิ์โดยใช้ Credential Manager API ในแอป Android

1. ก่อนเริ่มต้น

โซลูชันการตรวจสอบสิทธิ์แบบเดิมๆ ทำให้เกิดความท้าทายด้านความปลอดภัยและความสามารถในการใช้งานหลายประการ

รหัสผ่านมีการใช้งานอย่างแพร่หลาย แต่...

  • ลืมได้ง่าย
  • ผู้ใช้ต้องมีความรู้ในการสร้างรหัสผ่านที่รัดกุม
  • ผู้โจมตีสามารถฟิชชิง เก็บเกี่ยว และเล่นซ้ำได้ง่าย

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

Credential Manager รองรับพาสคีย์และรวมเข้ากับวิธีการตรวจสอบสิทธิ์แบบดั้งเดิม เช่น รหัสผ่าน การลงชื่อเข้าใช้ด้วย Google เป็นต้น

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

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

  • ลงชื่อสมัครใช้ : ใช้พาสคีย์และรหัสผ่าน
  • ลงชื่อเข้าใช้ : ใช้พาสคีย์และรหัสผ่านที่บันทึกไว้

ข้อกำหนดเบื้องต้น

  • ความเข้าใจพื้นฐานเกี่ยวกับวิธีเรียกใช้แอปใน Android Studio
  • ความเข้าใจพื้นฐานเกี่ยวกับขั้นตอนการตรวจสอบสิทธิ์ในแอป Android
  • ความเข้าใจพื้นฐานเกี่ยวกับพาสคีย์

สิ่งที่คุณจะได้เรียนรู้

  • วิธีสร้างพาสคีย์
  • วิธีบันทึกรหัสผ่านในเครื่องมือจัดการรหัสผ่าน
  • วิธีตรวจสอบสิทธิ์ผู้ใช้ด้วยพาสคีย์หรือรหัสผ่านที่บันทึกไว้

สิ่งที่คุณต้องมี

ชุดค่าผสมของอุปกรณ์อย่างใดอย่างหนึ่งต่อไปนี้

  • อุปกรณ์ Android ที่ใช้ Android 9 ขึ้นไป (สำหรับพาสคีย์) และ Android 4.4 ขึ้นไป(สำหรับการตรวจสอบสิทธิ์ด้วยรหัสผ่านผ่าน Credential Manager API)
  • อุปกรณ์ที่มีเซ็นเซอร์ไบโอเมตริก (หากเป็นไปได้)
  • อย่าลืมลงทะเบียนการล็อกหน้าจอ (ไบโอเมตริกหรืออื่นๆ)
  • เวอร์ชันปลั๊กอิน Kotlin : 1.8.10

2. ตั้งค่า

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

ใช้ debug.keystore เดียวกันกับที่กล่าวถึงในโปรเจ็กต์เพื่อสร้างบิลด์การแก้ไขข้อบกพร่องและรุ่นเผยแพร่เพื่อยืนยันลิงก์เนื้อหาดิจิทัลของชื่อแพ็กเกจและ SHA ในเซิร์ฟเวอร์จำลอง (ระบบดำเนินการนี้ให้คุณแล้วสำหรับแอปตัวอย่างใน build.gradle)

  1. โคลนที่เก็บนี้ในแล็ปท็อปจากสาขา credman_codelab: https://github.com/android/identity-samples/tree/credman_codelab
git clone -b credman_codelab https://github.com/android/identity-samples.git
  1. ไปที่โมดูล CredentialManager แล้วเปิดโปรเจ็กต์ใน Android Studio

มาดูสถานะเริ่มต้นของแอปกัน

หากต้องการดูว่าสถานะเริ่มต้นของแอปทำงานอย่างไร ให้ทำตามขั้นตอนต่อไปนี้

  1. เปิดแอป
  2. คุณจะเห็นหน้าจอหลักที่มีปุ่มลงชื่อสมัครใช้และลงชื่อเข้าใช้ ปุ่มเหล่านี้ยังไม่มีฟังก์ชันการทำงานใดๆ แต่เราจะเปิดใช้ฟังก์ชันการทำงานในส่วนที่จะกล่าวถึงต่อไป

7a6fe80f4cf877a8.jpeg

3. เพิ่มความสามารถในการลงชื่อสมัครใช้โดยใช้พาสคีย์

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

ตอนนี้คุณจะสร้างพาสคีย์และลงทะเบียนข้อมูลเข้าสู่ระบบของผู้ใช้โดยใช้ไบโอเมตริก/การล็อกหน้าจอ

ลงชื่อสมัครใช้ด้วยพาสคีย์

โค้ดภายใน CredentialManager/app/src/main/java/com/google/credentialmanager/sample/SignUpScreen.kt จะกำหนดช่องข้อความ "ชื่อผู้ใช้" และปุ่มลงชื่อสมัครใช้ด้วยพาสคีย์

1f4c50daa2551f1.jpeg

กำหนด Lambda createCredential() เพื่อใช้ใน View Model

ออบเจ็กต์เครื่องมือจัดการข้อมูลเข้าสู่ระบบต้องมี Activity ที่ส่งเข้ามา ซึ่งเชื่อมโยงกับหน้าจอ อย่างไรก็ตาม โดยปกติแล้วการดำเนินการของเครื่องมือจัดการข้อมูลเข้าสู่ระบบจะทริกเกอร์ใน View Model และไม่แนะนำให้อ้างอิงกิจกรรมภายใน View Model ดังนั้น เราจึงกำหนดฟังก์ชันเครื่องมือจัดการข้อมูลเข้าสู่ระบบในไฟล์แยก CredentialManagerUtil.kt และอ้างอิงฟังก์ชันเหล่านั้นในหน้าจอที่เหมาะสม จากนั้นส่งไปยัง View Model เป็นการเรียกกลับผ่านฟังก์ชัน Lambda

ค้นหาความคิดเห็น TODO ในฟังก์ชัน createCredential() ใน CredentialManagerUtil.kt และเรียกใช้ฟังก์ชัน CredentialManager.create() ดังนี้

CredentialManagerUtil.kt

suspend fun createCredential(
    activity: Activity,
    request: CreateCredentialRequest
): CreateCredentialResponse {
    TODO("Create a CredentialManager object and call createCredential() with a CreateCredentialRequest")
    val credentialManager = CredentialManager.create(activity)
    return credentialManager.createCredential(activity, request)
}

ส่งการท้าทายและการตอบกลับ JSON อื่นๆ ไปยังการเรียก createPasskey()

ก่อนที่จะสร้างพาสคีย์ คุณต้องขอข้อมูลที่จำเป็นจากเซิร์ฟเวอร์เพื่อส่งไปยัง Credential Manager API ในระหว่างการเรียก createCredential()

คุณมีคำตอบจำลองในชิ้นงานของโปรเจ็กต์อยู่แล้วชื่อ RegFromServer.txt ซึ่งจะแสดงผลพารามิเตอร์ที่จำเป็นในโค้ดแล็บนี้

  • ในแอป ให้ไปที่ SignUpViewModel.kt ค้นหาเมธอด signUpWithPasskeys ที่คุณจะเขียนตรรกะสำหรับการสร้างพาสคีย์และอนุญาตให้ผู้ใช้เข้าถึง คุณดูวิธีการได้ในคลาสเดียวกัน
  • ค้นหาบล็อกความคิดเห็น TODO ถึง create a CreatePublicKeyCredentialRequest() แล้วแทนที่ด้วยโค้ดต่อไปนี้

SignUpViewModel.kt

TODO("Create a CreatePublicKeyCredentialRequest() with necessary registration json from server")
    val request = CreatePublicKeyCredentialRequest(
        jsonProvider.fetchRegistrationJson()
            .replace("<userId>", getEncodedUserId())
            .replace("<userName>", _username.value)
            .replace("<userDisplayName>", _username.value)
            .replace("<challenge>", getEncodedChallenge())
    )

เมธอด jsonProvider.fetchRegistrationJsonFromServer() จะอ่านการตอบกลับ JSON ของเซิร์ฟเวอร์ที่จำลอง PublicKeyCredentialCreationOptions จากชิ้นงาน และส่งคืน JSON การลงทะเบียนเพื่อส่งต่อขณะสร้างพาสคีย์ เราจะแทนที่ค่าตัวยึดตำแหน่งบางค่าด้วยรายการที่ผู้ใช้ป้อนจากแอปของเราและฟิลด์จำลองบางรายการ

  • JSON นี้ไม่สมบูรณ์และมี 4 ฟิลด์ที่ต้องแทนที่
  • UserId ต้องไม่ซ้ำกันเพื่อให้ผู้ใช้สร้างพาสคีย์ได้หลายรายการ (หากจำเป็น) แทนที่ <userId> ด้วยค่า userId ที่สร้างขึ้น
  • <challenge> ต้องไม่ซ้ำกันด้วย คุณจึงต้องสร้างคำท้าที่ไม่ซ้ำกันแบบสุ่ม วิธีการนี้อยู่ในโค้ดของคุณแล้ว

PublicKeyCredentialCreationOptionsการตอบกลับจากเซิร์ฟเวอร์จริงอาจแสดงตัวเลือกเพิ่มเติม ตัวอย่างของช่องเหล่านี้มีดังนี้

{
  "challenge": String,
  "rp": {
    "name": String,
    "id": String
  },
  "user": {
    "id": String,
    "name": String,
    "displayName": String
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

ตารางต่อไปนี้อธิบายพารามิเตอร์ที่สำคัญบางอย่างในออบเจ็กต์ PublicKeyCredentialCreationOptions

พารามิเตอร์

คำอธิบาย

challenge

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

user.id

รหัสที่ไม่ซ้ำกันของผู้ใช้ ค่านี้ต้องไม่มีข้อมูลส่วนบุคคลที่ระบุตัวบุคคลนั้นได้ เช่น อีเมลหรือชื่อผู้ใช้ ค่าแบบสุ่มขนาด 16 ไบต์ที่สร้างขึ้นต่อบัญชีจะใช้งานได้ดี

user.name

ฟิลด์นี้ควรมีตัวระบุที่ไม่ซ้ำกันสำหรับบัญชีที่ผู้ใช้จะจดจำได้ เช่น อีเมลหรือชื่อผู้ใช้ ซึ่งจะแสดงในตัวเลือกบัญชี (หากใช้ชื่อผู้ใช้ ให้ใช้ค่าเดียวกับการตรวจสอบสิทธิ์ด้วยรหัสผ่าน)

user.displayName

ช่องนี้เป็นชื่อบัญชีที่ไม่บังคับซึ่งเรียกง่ายกว่า

rp.id

เอนทิตี Relying Party จะสอดคล้องกับรายละเอียดแอปพลิเคชันของคุณ โดยมีแอตทริบิวต์ต่อไปนี้

  • name (ต้องระบุ): ชื่อแอปพลิเคชัน
  • ID (ไม่บังคับ): สอดคล้องกับโดเมนหรือโดเมนย่อย หากไม่มี ระบบจะใช้โดเมนปัจจุบัน
  • icon (ไม่บังคับ)

pubKeyCredParams

รายการอัลกอริทึมและประเภทคีย์ที่อนุญาต รายการนี้ต้องมีองค์ประกอบอย่างน้อย 1 รายการ

excludeCredentials

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

authenticatorSelection.authenticatorAttachment

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

residentKey

ระบุค่า required เพื่อสร้างพาสคีย์

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

  1. เมื่อสร้าง CreatePublicKeyCredentialRequest() แล้ว คุณต้องเรียกใช้การเรียก createCredential() ด้วยคำขอที่สร้างขึ้น

SignUpViewModel.kt

try {
   TODO("Call createCredential() with createPublicKeyCredentialRequest")
   createCredential(request)
   TODO("Complete the registration process after sending public key credential to your server and let the user in")

} catch (e: CreateCredentialException) {
   handlePasskeyFailure(e)
}

  • คุณจัดการระดับการมองเห็นของมุมมองที่แสดงผลและจัดการข้อยกเว้นหากคำขอไม่สำเร็จหรือล้มเหลวเนื่องจากเหตุผลบางประการ ระบบจะบันทึกข้อความแสดงข้อผิดพลาดและแสดงในแอปในกล่องโต้ตอบข้อผิดพลาด คุณตรวจสอบบันทึกข้อผิดพลาดแบบเต็มได้ผ่าน Android Studio หรือคำสั่ง adb debug

1ea8ace66135de1e.png