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

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

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

รหัสผ่านเป็นสิ่งที่ใช้กันอย่างแพร่หลาย แต่...

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

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

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

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

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

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

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

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

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

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

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

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

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

2. ตั้งค่า

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

ออบเจ็กต์ของเครื่องมือจัดการข้อมูลเข้าสู่ระบบต้องส่ง 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

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

  • 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

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

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

ในเมธอด signUpWithPasskeys() ให้ค้นหาความคิดเห็นที่เกี่ยวข้องและแทนที่ด้วยโค้ดต่อไปนี้

SignUpViewModel.kt

try {
    createCredential(request)
    TODO("Complete the registration process after sending public key credential to your server and let the user in")
registerResponse()
    DataProvider.setSignedInThroughPasskeys(true)
    _navigationEvent.emit(NavigationEvent.NavigateToHome(signedInWithPasskeys = true))
} catch (e: CreateCredentialException) {
   handlePasskeyFailure(e)
}
  • registerResponse() จะแสดง true ซึ่งบ่งบอกว่าเซิร์ฟเวอร์จำลองได้บันทึกคีย์สาธารณะไว้ใช้ในอนาคตแล้ว
  • ตั้งค่าสถานะ setSignedInThroughPasskeys เป็น true
  • เมื่อเข้าสู่ระบบแล้ว ให้เปลี่ยนเส้นทางผู้ใช้ไปยังหน้าจอหลัก

PublicKeyCredential จริงอาจมีฟิลด์มากกว่านี้ ตัวอย่างของฟิลด์เหล่านี้แสดงอยู่ด้านล่าง

{
  "id": String,
  "rawId": String,
  "type": "public-key",
  "response": {
    "clientDataJSON": String,
    "attestationObject": String,
  }
}

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

พารามิเตอร์

คำอธิบาย

id

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

rawId

ArrayBuffer เวอร์ชันออบเจ็กต์ของรหัสข้อมูลเข้าสู่ระบบ

response.clientDataJSON

ออบเจ็กต์ ArrayBuffer ที่เข้ารหัสข้อมูลไคลเอ็นต์

response.attestationObject

ออบเจ็กต์การรับรองที่เข้ารหัส ArrayBuffer โดยมีข้อมูลสำคัญ เช่น รหัส RP, แฟล็ก และคีย์สาธารณะ

เรียกใช้แอป แล้วคุณจะคลิกปุ่มลงชื่อสมัครใช้ด้วยพาสคีย์และสร้างพาสคีย์ได้

4. บันทึกรหัสผ่านในผู้ให้บริการข้อมูลเข้าสู่ระบบ

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

หากต้องการบันทึกข้อมูลเข้าสู่ระบบรหัสผ่านของผู้ใช้กับผู้ให้บริการรหัสผ่าน คุณจะต้องใช้ CreatePasswordRequest เพื่อส่งไปยัง createCredential() เพื่อบันทึกรหัสผ่าน

  • ค้นหาเมธอด signUpWithPassword() แล้วแทนที่ TODO ด้วยการเรียก createPassword ดังนี้

SignUpViewModel.kt

TODO("CreatePasswordRequest with entered username and password")
    val passwordRequest = CreatePasswordRequest(_username.value, _password.value)

  • จากนั้นสร้างข้อมูลเข้าสู่ระบบด้วยคำขอสร้างรหัสผ่าน และบันทึกข้อมูลเข้าสู่ระบบรหัสผ่านของผู้ใช้กับผู้ให้บริการรหัสผ่าน จากนั้นให้ผู้ใช้เข้าสู่ระบบ เราจะตรวจพบข้อยกเว้นที่เกิดขึ้นในโฟลว์นี้ในลักษณะทั่วไปมากขึ้น แทนที่ TODO ด้วยโค้ดต่อไปนี้

SignUpViewModel.kt

TODO("Create credential with created password request and log the user in")
    try {
        createCredential(passwordRequest)
        simulateServerDelayAndLogIn()
    } catch (e: Exception) {
        val errorMessage = "Exception Message : " + e.message
        Log.e("Auth", errorMessage)
        _passwordCreationError.value = errorMessage
        _isLoading.value = false
    }

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

5. เพิ่มความสามารถในการตรวจสอบสิทธิ์ด้วยพาสคีย์หรือรหัสผ่าน

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

76e81460b26f9798.png

กำหนด Lambda getCredential() เพื่อใช้ใน ViewModel

เช่นเดียวกับก่อนหน้านี้ เราจะเรียกใช้ getCredential() ของเครื่องมือจัดการข้อมูลเข้าสู่ระบบในไฟล์แยก CredentialManagerUtil.kt เพื่อใช้อ้างอิงในหน้าจอที่เหมาะสมและส่งไปยัง View Model เป็นการเรียกกลับผ่านฟังก์ชัน Lambda

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

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

รับคำท้าและตัวเลือกอื่นๆ เพื่อส่งไปยังการเรียกใช้ getPasskey()

ก่อนขอให้ผู้ใช้ตรวจสอบสิทธิ์ คุณต้องขอพารามิเตอร์ที่จะส่งใน WebAuthn JSON จากเซิร์ฟเวอร์ รวมถึงการท้าทาย

คุณมีคำตอบจำลองในชิ้นงาน (AuthFromServer.txt) อยู่แล้ว ซึ่งจะแสดงพารามิเตอร์ดังกล่าวในโค้ดแล็บนี้

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

SignInViewModel.kt

TODO("Create a GetPublicKeyCredentialOption() with necessary authentication json from server")
    val getPublicKeyCredentialOption =
        GetPublicKeyCredentialOption(jsonProvider.fetchAuthJson(), null)

เมธอด fetchAuthJsonFromServer() จะอ่านการตอบกลับ JSON ของการตรวจสอบสิทธิ์จากเนื้อหาและส่งคืน JSON ของการตรวจสอบสิทธิ์เพื่อดึงพาสคีย์ทั้งหมดที่เชื่อมโยงกับบัญชีผู้ใช้นี้

พารามิเตอร์ที่ 2 ของ GetPublicKeyCredentialOption() คือ clientDataHash ซึ่งเป็นแฮชที่ใช้เพื่อยืนยันข้อมูลประจำตัวของ Relying Party ตั้งค่านี้ก็ต่อเมื่อคุณได้ตั้งค่า GetCredentialRequest.origin แล้ว สำหรับแอปตัวอย่าง ระบบจะตั้งค่านี้เป็น null

หมายเหตุ : เซิร์ฟเวอร์ของโค้ดแล็บนี้ออกแบบมาเพื่อแสดง JSON ที่คล้ายกับพจนานุกรม PublicKeyCredentialRequestOptions ที่ส่งไปยังการเรียก getCredential() ของ API ให้มากที่สุด ข้อมูลโค้ดต่อไปนี้มีตัวอย่างตัวเลือก 2-3 รายการที่คุณอาจได้รับในการตอบกลับจริง

{
  "challenge": String,
  "rpId": String,
  "userVerification": "",
  "timeout": 1800000
}

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

พารามิเตอร์

คำอธิบาย

challenge

คำท้าที่เซิร์ฟเวอร์สร้างขึ้นในออบเจ็กต์ ArrayBuffer ซึ่งจำเป็นต่อการป้องกันการโจมตีแบบส่งซ้ำ อย่ารับคำท้าเดียวกันในคำตอบ 2 ครั้ง ถือว่าเป็นโทเค็น CSRF

rpId

รหัส RP คือโดเมน เว็บไซต์ระบุได้ทั้งโดเมนหรือคำต่อท้ายที่จดทะเบียนได้ ค่านี้ต้องตรงกับพารามิเตอร์ rp.id ที่ใช้เมื่อสร้างพาสคีย์

  • จากนั้นคุณต้องสร้างออบเจ็กต์ PasswordOption() เพื่อดึงรหัสผ่านที่บันทึกไว้ทั้งหมดในผู้ให้บริการรหัสผ่านผ่าน Credential Manager API สำหรับบัญชีผู้ใช้นี้ ใน getSavedCredentials() method ให้ค้นหา TODO แล้วแทนที่ด้วยข้อมูลต่อไปนี้

SigninViewModel.kt

TODO("Create a PasswordOption to retrieve all the associated user's password")

val getPasswordOption = GetPasswordOption()

รวมสิ่งเหล่านี้เป็น GetCredentialRequest

SigninViewModel.kt

TODO("Combine requests into a GetCredentialRequest")
    val request = GetCredentialRequest(
        listOf(
            getPublicKeyCredentialOption,
            getPasswordOption
        )
    )

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

จากนั้นคุณต้องเรียกใช้คำขอ getCredential() โดยใช้ตัวเลือกทั้งหมดข้างต้นเพื่อดึงข้อมูลเข้าสู่ระบบที่เชื่อมโยง

SignInViewModel.kt

try {
    TODO("Call getCredential() with required credential options")
    val result = getCredential(request)

    val data = when (result.credential) {
        is PublicKeyCredential -> {
            val cred = result.credential as PublicKeyCredential
            DataProvider.setSignedInThroughPasskeys(true)
            "Passkey: ${cred.authenticationResponseJson}"
        }

        is PasswordCredential -> {
            val cred = result.credential as PasswordCredential
            DataProvider.setSignedInThroughPasskeys(false)
            "Got Password - User:${cred.id} Password: ${cred.password}"
        }

        is CustomCredential -> {
            //If you are also using any external sign-in libraries, parse them here with the utility functions provided.
            null
        }

        else -> null
    }

    TODO("Complete the authentication process after validating the public key credential to your server and let the user in.")
    } catch (e: Exception) {
        Log.e("Auth", "getCredential failed with exception: " + e.message.toString())
        _signInError.value =
            "An error occurred while authenticating: " + e.message.toString()
    } finally {
        _isLoading.value = false
    }
  • คุณส่งข้อมูลที่จำเป็นไปยัง getCredential() ซึ่งจะใช้รายการตัวเลือกข้อมูลเข้าสู่ระบบและบริบทกิจกรรมเพื่อแสดงตัวเลือกใน BottomSheet ในบริบทนั้น
  • เมื่อคำขอสำเร็จ คุณจะเห็น BottomSheet บนหน้าจอซึ่งแสดงข้อมูลเข้าสู่ระบบทั้งหมดที่สร้างขึ้นสำหรับบัญชีที่เชื่อมโยง
  • ตอนนี้ผู้ใช้สามารถยืนยันตัวตนผ่านข้อมูลไบโอเมตริกหรือการล็อกหน้าจอ ฯลฯ เพื่อตรวจสอบสิทธิ์ของข้อมูลเข้าสู่ระบบที่เลือกได้แล้ว
  • หากข้อมูลเข้าสู่ระบบที่เลือกเป็น PublicKeyCredential ให้ตั้งค่าสถานะ setSignedInThroughPasskeys เป็น true มิเช่นนั้น ให้ตั้งค่าเป็น false

ข้อมูลโค้ดต่อไปนี้มีตัวอย่างออบเจ็กต์ PublicKeyCredential

{
  "id": String
  "rawId": String
  "type": "public-key",
  "response": {
    "clientDataJSON": String
    "authenticatorData": String
    "signature": String
    "userHandle": String
  }
}

ตารางต่อไปนี้ไม่ได้ครอบคลุมทั้งหมด แต่มีพารามิเตอร์ที่สำคัญในออบเจ็กต์ PublicKeyCredential

พารามิเตอร์

คำอธิบาย

id

รหัสที่เข้ารหัส Base64URL ของข้อมูลเข้าสู่ระบบพาสคีย์ที่ได้รับการตรวจสอบสิทธิ์

rawId

ArrayBuffer เวอร์ชันออบเจ็กต์ของรหัสข้อมูลเข้าสู่ระบบ

response.clientDataJSON

ออบเจ็กต์ ArrayBuffer ของข้อมูลลูกค้า ฟิลด์นี้มีข้อมูล เช่น คำท้าและต้นทางที่เซิร์ฟเวอร์ RP ต้องยืนยัน

response.authenticatorData

ออบเจ็กต์ ArrayBuffer ของข้อมูลเครื่องมือตรวจสอบสิทธิ์ ฟิลด์นี้มีข้อมูล เช่น รหัส RP

response.signature

ออบเจ็กต์ ArrayBuffer ของลายเซ็น ค่านี้เป็นหัวใจสำคัญของข้อมูลเข้าสู่ระบบและต้องได้รับการยืนยันในเซิร์ฟเวอร์

response.userHandle

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

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

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

ในเมธอด signInWithSavedCredentials() ให้ค้นหาความคิดเห็นที่เกี่ยวข้อง แล้วแทนที่ด้วยโค้ดต่อไปนี้

SignInViewModel.kt

TODO("Complete the authentication process after validating the public key credential to your server and let the user in.")
    if (data != null) {
        sendSignInResponseToServer()
        _navigationEvent.emit(NavigationEvent.NavigateToHome(signedInWithPasskeys = DataProvider.isSignedInThroughPasskeys()))
    }
  • sendSigninResponseToServer() จะแสดงผลเป็นจริง ซึ่งบ่งชี้ว่าเซิร์ฟเวอร์ (จำลอง) ได้ตรวจสอบคีย์สาธารณะเพื่อใช้ในอนาคตแล้ว
  • เมื่อเข้าสู่ระบบแล้ว ให้เปลี่ยนเส้นทางผู้ใช้ไปยังหน้าจอหลัก

เรียกใช้แอป แล้วไปที่ลงชื่อเข้าใช้ > ลงชื่อเข้าใช้ด้วยพาสคีย์/รหัสผ่านที่บันทึกไว้ แล้วลองลงชื่อเข้าใช้โดยใช้ข้อมูลเข้าสู่ระบบที่บันทึกไว้

ลองใช้

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

6. ยินดีด้วย

คุณทำ Codelab นี้เสร็จแล้ว หากต้องการดูโซลูชันสุดท้ายซึ่งมีอยู่ที่ https://github.com/android/identity-samples/tree/main/CredentialManager

หากมีคำถาม โปรดถามใน StackOverflow โดยใช้แท็ก passkey

ดูข้อมูลเพิ่มเติม