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)
- โคลนที่เก็บนี้ในแล็ปท็อปจากสาขา credman_codelab: https://github.com/android/identity-samples/tree/credman_codelab
git clone -b credman_codelab https://github.com/android/identity-samples.git
- ไปที่โมดูล CredentialManager แล้วเปิดโปรเจ็กต์ใน Android Studio
มาดูสถานะเริ่มต้นของแอปกัน
หากต้องการดูว่าสถานะเริ่มต้นของแอปทำงานอย่างไร ให้ทำตามขั้นตอนต่อไปนี้
- เปิดแอป
- คุณจะเห็นหน้าจอหลักที่มีปุ่มลงชื่อสมัครใช้และลงชื่อเข้าใช้ ปุ่มเหล่านี้ยังไม่มีฟังก์ชันการทำงานใดๆ แต่เราจะเปิดใช้ฟังก์ชันการทำงานในส่วนที่จะกล่าวถึงต่อไป

3. เพิ่มความสามารถในการลงชื่อสมัครใช้โดยใช้พาสคีย์
เมื่อลงชื่อสมัครใช้บัญชีใหม่ในแอป Android ที่ใช้ Credential Manager API ผู้ใช้จะสร้างพาสคีย์สำหรับบัญชีของตนได้ ระบบจะจัดเก็บพาสคีย์นี้อย่างปลอดภัยไว้ในผู้ให้บริการข้อมูลเข้าสู่ระบบที่ผู้ใช้เลือก และจะใช้สำหรับการลงชื่อเข้าใช้ในอนาคตโดยไม่ต้องให้ผู้ใช้ป้อนรหัสผ่านทุกครั้ง
ตอนนี้คุณจะสร้างพาสคีย์และลงทะเบียนข้อมูลเข้าสู่ระบบของผู้ใช้โดยใช้ไบโอเมตริก/การล็อกหน้าจอ
ลงชื่อสมัครใช้ด้วยพาสคีย์
โค้ดภายใน CredentialManager/app/src/main/java/com/google/credentialmanager/sample/SignUpScreen.kt จะกำหนดช่องข้อความ "ชื่อผู้ใช้" และปุ่มลงชื่อสมัครใช้ด้วยพาสคีย์

กำหนด 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
พารามิเตอร์ | คำอธิบาย |
สตริงแบบสุ่มที่เซิร์ฟเวอร์สร้างขึ้นซึ่งมีเอนโทรปีเพียงพอที่จะทำให้การคาดเดาเป็นไปไม่ได้ โดยควรมีความยาวอย่างน้อย 16 ไบต์ ต้องระบุแต่จะไม่ได้ใช้ในระหว่างการลงทะเบียน เว้นแต่จะทำการรับรอง | |
รหัสที่ไม่ซ้ำกันของผู้ใช้ ค่านี้ต้องไม่มีข้อมูลส่วนบุคคลที่ระบุตัวบุคคลนั้นได้ เช่น อีเมลหรือชื่อผู้ใช้ ค่าแบบสุ่มขนาด 16 ไบต์ที่สร้างขึ้นต่อบัญชีจะใช้งานได้ดี | |
ฟิลด์นี้ควรมีตัวระบุที่ไม่ซ้ำกันสำหรับบัญชีที่ผู้ใช้จะจดจำได้ เช่น อีเมลหรือชื่อผู้ใช้ ซึ่งจะแสดงในตัวเลือกบัญชี (หากใช้ชื่อผู้ใช้ ให้ใช้ค่าเดียวกับการตรวจสอบสิทธิ์ด้วยรหัสผ่าน) | |
ช่องนี้เป็นชื่อบัญชีที่ไม่บังคับซึ่งเรียกง่ายกว่า | |
เอนทิตี Relying Party จะสอดคล้องกับรายละเอียดแอปพลิเคชันของคุณ โดยมีแอตทริบิวต์ต่อไปนี้
| |
รายการอัลกอริทึมและประเภทคีย์ที่อนุญาต รายการนี้ต้องมีองค์ประกอบอย่างน้อย 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