Credential Manager คือ Jetpack API ที่รองรับวิธีการลงชื่อเข้าใช้หลายวิธี เช่น ชื่อผู้ใช้และรหัสผ่าน พาสคีย์ และโซลูชันการลงชื่อเข้าใช้แบบรวมศูนย์ (เช่น ฟีเจอร์ลงชื่อเข้าใช้ด้วย Google) ใน API เดียว ซึ่งช่วยให้นักพัฒนาแอปผสานรวมได้ง่ายขึ้น
นอกจากนี้ สำหรับผู้ใช้ เครื่องมือจัดการข้อมูลเข้าสู่ระบบจะรวมการลงชื่อเข้าใช้เข้าด้วยกัน ของวิธีการตรวจสอบสิทธิ์ทั้งหมด ทำให้ผู้ใช้เข้าใจและเห็นได้ง่าย เพื่อลงชื่อเข้าใช้แอปต่างๆ ได้ไม่ว่าจะเลือกใช้วิธีใดก็ตาม
หน้านี้อธิบายแนวคิดของพาสคีย์และขั้นตอนในการใช้งานการสนับสนุนฝั่งไคลเอ็นต์สำหรับโซลูชันการตรวจสอบสิทธิ์ ซึ่งรวมถึงพาสคีย์ โดยใช้ Credential Manager API นอกจากนี้ยังมีหน้าคำถามที่พบบ่อยแยกต่างหากซึ่งให้คำตอบสำหรับคำถามที่เฉพาะเจาะจงและละเอียดยิ่งขึ้น
ความคิดเห็นของคุณเป็นส่วนสำคัญในการปรับปรุง API เครื่องมือจัดการการรับรอง แชร์ ปัญหาที่พบหรือแนวคิดในการปรับปรุง API โดยใช้ลิงก์ต่อไปนี้
เกี่ยวกับพาสคีย์
พาสคีย์เป็นวิธีแทนที่รหัสผ่านได้อย่างปลอดภัยและง่ายดายยิ่งขึ้น พาสคีย์ ผู้ใช้ลงชื่อเข้าใช้แอปและเว็บไซต์โดยใช้เซ็นเซอร์ข้อมูลไบโอเมตริก (เช่น ลายนิ้วมือหรือการจดจำใบหน้า) PIN หรือรูปแบบ ซึ่งจะช่วยให้ผู้ใช้ลงชื่อเข้าใช้ได้อย่างราบรื่นโดยไม่ต้องจำชื่อผู้ใช้หรือรหัสผ่าน
พาสคีย์อาศัย WebAuthn (การตรวจสอบสิทธิ์เว็บ) ซึ่งเป็นมาตรฐานที่พัฒนาร่วมกันโดย FIDO Alliance และ World Wide Web Consortium (W3C) WebAuthn ใช้วิทยาการเข้ารหัสคีย์สาธารณะเพื่อตรวจสอบสิทธิ์ผู้ใช้ เว็บไซต์หรือแอปที่ผู้ใช้ลงชื่อเข้าใช้จะเห็นและจัดเก็บคีย์สาธารณะได้ แต่จะไม่เห็นคีย์ส่วนตัว คีย์ส่วนตัวจะเก็บเป็นความลับและปลอดภัย เนื่องจากหัวใจสำคัญไม่เหมือนใคร พาสคีย์เชื่อมโยงกับเว็บไซต์หรือแอปจึงทำฟิชชิงไม่ได้ จึงช่วยเพิ่มความปลอดภัยอีกขั้น
เครื่องมือจัดการข้อมูลเข้าสู่ระบบช่วยให้ผู้ใช้สร้างและจัดเก็บพาสคีย์ใน Google เครื่องมือจัดการรหัสผ่าน
อ่านการตรวจสอบสิทธิ์ผู้ใช้ด้วยพาสคีย์เพื่อดูคำแนะนำเกี่ยวกับวิธีใช้ขั้นตอนการตรวจสอบสิทธิ์ด้วยพาสคีย์อย่างราบรื่นด้วยเครื่องมือจัดการข้อมูลเข้าสู่ระบบ
สิ่งที่ต้องมีก่อน
หากต้องการใช้เครื่องมือจัดการข้อมูลเข้าสู่ระบบ ให้ทำตามขั้นตอนในส่วนนี้
ใช้แพลตฟอร์มเวอร์ชันล่าสุด
เครื่องมือจัดการข้อมูลเข้าสู่ระบบใช้ได้ใน Android 4.4 (API ระดับ 19) ขึ้นไป
เพิ่มการพึ่งพาไปยังแอป
เพิ่มการพึ่งพาต่อไปนี้ลงในสคริปต์บิลด์ของโมดูลแอป
Kotlin
dependencies { implementation("androidx.credentials:credentials:1.5.0-alpha05") // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation("androidx.credentials:credentials-play-services-auth:1.5.0-alpha05") }
ดึงดูด
dependencies { implementation "androidx.credentials:credentials:1.5.0-alpha05" // optional - needed for credentials support from play services, for devices running // Android 13 and below. implementation "androidx.credentials:credentials-play-services-auth:1.5.0-alpha05" }
เก็บรักษาคลาสในไฟล์ ProGuard
ในไฟล์ proguard-rules.pro
ของโมดูล ให้เพิ่มคำสั่งต่อไปนี้
-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
*;
}
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีลดขนาด สร้างความสับสน และเพิ่มประสิทธิภาพแอป
เพิ่มการรองรับลิงก์เนื้อหาดิจิทัล
หากต้องการเปิดใช้การรองรับพาสคีย์สําหรับแอป Android ให้เชื่อมโยงแอปกับเว็บไซต์ที่แอปเป็นเจ้าของ คุณสามารถประกาศการเชื่อมโยงนี้ได้โดยทำตามขั้นตอนต่อไปนี้
สร้างไฟล์ JSON ของลิงก์เนื้อหาดิจิทัล (Digital Asset Links) ตัวอย่างเช่น หากต้องการประกาศว่าเว็บไซต์
https://signin.example.com
และแอป Android ที่มีชื่อแพ็กเกจcom.example
สามารถแชร์ข้อมูลเข้าสู่ระบบ ให้สร้างไฟล์ชื่อassetlinks.json
ที่มีเนื้อหาต่อไปนี้[ { "relation" : [ "delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds" ], "target" : { "namespace" : "android_app", "package_name" : "com.example.android", "sha256_cert_fingerprints" : [ SHA_HEX_VALUE ] } } ]
ฟิลด์
relation
คืออาร์เรย์สตริงอย่างน้อย 1 รายการที่อธิบายความสัมพันธ์ที่ประกาศ เพื่อประกาศว่าแอปและเว็บไซต์ใช้การลงชื่อเข้าใช้ร่วมกัน ข้อมูลเข้าสู่ระบบ ให้ระบุความสัมพันธ์เป็นdelegate_permission/handle_all_urls
และdelegate_permission/common.get_login_creds
ฟิลด์
target
คือออบเจ็กต์ที่ระบุเนื้อหาที่ใช้ประกาศ ฟิลด์ต่อไปนี้ระบุเว็บไซต์namespace
web
site
URL ของเว็บไซต์ในรูปแบบ
https://domain[:optional_port]
; สำหรับ ตัวอย่างเช่นhttps://www.example.com
domain ต้องเป็นแบบเต็มและ ต้องละเว้น optional_port เมื่อใช้พอร์ต 443 สำหรับ HTTPS
เป้าหมาย
site
ต้องเป็นโดเมนรูทเท่านั้น คุณไม่สามารถจํากัดการเชื่อมโยงแอปกับไดเรกทอรีย่อยที่เฉพาะเจาะจงได้ อย่าใส่ ใน URL เช่น เครื่องหมายทับปิดท้ายโดเมนย่อยไม่ถือว่าตรงกัน กล่าวคือ ถ้าคุณระบุ domain เป็น
www.example.com
, โดเมนwww.counter.example.com
ไม่ได้เชื่อมโยงกับแอปของคุณฟิลด์ต่อไปนี้จะระบุแอป Android
namespace
android_app
package_name
ชื่อแพ็กเกจที่ประกาศในไฟล์ Manifest ของแอป ตัวอย่างเช่น com.example.android
sha256_cert_fingerprints
ลายนิ้วมือ SHA256 ของใบรับรองการรับรองการลงนามของแอป โฮสต์ไฟล์ JSON ของลิงก์เนื้อหาดิจิทัล (Digital Asset Links) ในตำแหน่งต่อไปนี้ในโดเมนการลงชื่อเข้าใช้
https://domain[:optional_port]/.well-known/assetlinks.json
ตัวอย่างเช่น หากโดเมนที่ลงชื่อเข้าใช้ของคุณคือ
signin.example.com
ให้โฮสต์ JSON ไฟล์ที่https://signin.example.com/.well-known/assetlinks.json
ประเภท MIME สำหรับไฟล์ลิงก์เนื้อหาดิจิทัลต้องเป็น JSON ตรวจสอบว่า เซิร์ฟเวอร์จะส่งส่วนหัว
Content-Type: application/json
ในการตอบกลับตรวจสอบว่าโฮสต์อนุญาตให้ Google เรียกข้อมูลลิงก์เนื้อหาดิจิทัลของคุณ หากคุณมีไฟล์
robots.txt
ไฟล์ดังกล่าวต้องอนุญาตให้ตัวแทน Googlebot ดึงข้อมูล/.well-known/assetlinks.json
เว็บไซต์ส่วนใหญ่อนุญาต Agent อัตโนมัติเพื่อเรียกดูไฟล์ในเส้นทาง/.well-known/
เพื่อให้ บริการสามารถเข้าถึงข้อมูลเมตาในไฟล์เหล่านั้นได้:User-agent: * Allow: /.well-known/
เพิ่มบรรทัดต่อไปนี้ลงในไฟล์ Manifest ในส่วน
<application>
<meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
หากคุณใช้การลงชื่อเข้าใช้ด้วยรหัสผ่านผ่านเครื่องมือจัดการข้อมูลเข้าสู่ระบบ ให้ทำตาม ขั้นตอนนี้เพื่อกำหนดค่าการลิงก์เนื้อหาดิจิทัลในไฟล์ Manifest ขั้นตอนนี้ จำเป็นหากคุณใช้พาสคีย์เพียงอย่างเดียว
ประกาศการเชื่อมโยงในแอป Android เพิ่มออบเจ็กต์ที่ระบุไฟล์
assetlinks.json
ที่จะโหลด คุณต้องหลีกเลี่ยงเครื่องหมายอะพอสทรอฟีและ เครื่องหมายคำพูดที่คุณใช้ในสตริง เช่น<string name="asset_statements" translatable="false"> [{ \"include\": \"https://signin.example.com/.well-known/assetlinks.json\" }] </string>
> GET /.well-known/assetlinks.json HTTP/1.1 > User-Agent: curl/7.35.0 > Host: signin.example.com < HTTP/1.1 200 OK < Content-Type: application/json
กำหนดค่าเครื่องมือจัดการข้อมูลเข้าสู่ระบบ
หากต้องการกําหนดค่าและเริ่มต้นวัตถุ CredentialManager
ให้เพิ่มตรรกะคล้ายกับตัวอย่างต่อไปนี้
Kotlin
// Use your app or activity context to instantiate a client instance of // CredentialManager. val credentialManager = CredentialManager.create(context)
Java
// Use your app or activity context to instantiate a client instance of // CredentialManager. CredentialManager credentialManager = CredentialManager.create(context)
ระบุช่องข้อมูลเข้าสู่ระบบ
ใน Android 14 ขึ้นไป คุณสามารถใช้แอตทริบิวต์ isCredential
เพื่อ
ระบุช่องข้อมูลเข้าสู่ระบบ เช่น ช่องชื่อผู้ใช้หรือรหัสผ่าน แอตทริบิวต์นี้
ระบุว่ามุมมองนี้เป็นฟิลด์ข้อมูลเข้าสู่ระบบที่มีไว้ใช้งาน
เครื่องมือจัดการข้อมูลเข้าสู่ระบบและผู้ให้บริการข้อมูลเข้าสู่ระบบบุคคลที่สาม พร้อมช่วยป้อนข้อความอัตโนมัติ
จะให้คำแนะนำในการป้อนข้อความอัตโนมัติที่ดีกว่า เมื่อแอปใช้ข้อมูลเข้าสู่ระบบ
Manager API, Bottom Sheet ของเครื่องมือจัดการข้อมูลเข้าสู่ระบบที่มีข้อมูลเข้าสู่ระบบที่ใช้ได้คือ
แสดงอยู่ และไม่จำเป็นต้องแสดง
กล่องโต้ตอบการกรอกข้อความ
ของการป้อนข้อความอัตโนมัติอีก
ชื่อผู้ใช้หรือรหัสผ่าน ในทํานองเดียวกัน คุณไม่จําเป็นต้องแสดงกล่องโต้ตอบบันทึกของฟีเจอร์ป้อนข้อความอัตโนมัติสําหรับรหัสผ่าน เนื่องจากแอปจะขอ Credential Manager API เพื่อบันทึกข้อมูลเข้าสู่ระบบ
หากต้องการใช้แอตทริบิวต์ isCredential
ให้เพิ่มแอตทริบิวต์ลงในข้อมูลพร็อพเพอร์ตี้ที่เกี่ยวข้อง ดังนี้
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:isCredential="true"
...
/>
ลงชื่อเข้าใช้ให้ผู้ใช้
หากต้องการเรียกข้อมูลพาสคีย์และตัวเลือกรหัสผ่านทั้งหมดที่เชื่อมโยงกับบัญชีของผู้ใช้ ให้ทำตามขั้นตอนต่อไปนี้
เริ่มใช้ตัวเลือกการตรวจสอบสิทธิ์รหัสผ่านและพาสคีย์ดังนี้
Kotlin
// Retrieves the user's saved password for your app from their // password provider. val getPasswordOption = GetPasswordOption() // Get passkey from the user's public key credential provider. val getPublicKeyCredentialOption = GetPublicKeyCredentialOption( requestJson = requestJson )
Java
// Retrieves the user's saved password for your app from their // password provider. GetPasswordOption getPasswordOption = new GetPasswordOption(); // Get passkey from the user's public key credential provider. GetPublicKeyCredentialOption getPublicKeyCredentialOption = new GetPublicKeyCredentialOption(requestJson);
ใช้ตัวเลือกที่ดึงมาจากขั้นตอนก่อนหน้าเพื่อสร้าง คำขอลงชื่อเข้าใช้
Kotlin
val getCredRequest = GetCredentialRequest( listOf(getPasswordOption, getPublicKeyCredentialOption) )
Java
GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder() .addCredentialOption(getPasswordOption) .addCredentialOption(getPublicKeyCredentialOption) .build();
เปิดขั้นตอนการลงชื่อเข้าใช้
Kotlin
coroutineScope.launch { try { val result = credentialManager.getCredential( // Use an activity-based context to avoid undefined system UI // launching behavior. context = activityContext, request = getCredRequest ) handleSignIn(result) } catch (e : GetCredentialException) { handleFailure(e) } } fun handleSignIn(result: GetCredentialResponse) { // Handle the successfully returned credential. val credential = result.credential when (credential) { is PublicKeyCredential -> { val responseJson = credential.authenticationResponseJson // Share responseJson i.e. a GetCredentialResponse on your server to // validate and authenticate } is PasswordCredential -> { val username = credential.id val password = credential.password // Use id and password to send to your server to validate // and authenticate } is CustomCredential -> { // If you are also using any external sign-in libraries, parse them // here with the utility functions provided. if (credential.type == ExampleCustomCredential.TYPE) { try { val ExampleCustomCredential = ExampleCustomCredential.createFrom(credential.data) // Extract the required credentials and complete the authentication as per // the federated sign in or any external sign in library flow } catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) { // Unlikely to happen. If it does, you likely need to update the dependency // version of your external sign-in library. Log.e(TAG, "Failed to parse an ExampleCustomCredential", e) } } else { // Catch any unrecognized custom credential type here. Log.e(TAG, "Unexpected type of credential") } } else -> { // Catch any unrecognized credential type here. Log.e(TAG, "Unexpected type of credential") } } }
Java
credentialManager.getCredentialAsync( // Use activity based context to avoid undefined // system UI launching behavior activity, getCredRequest, cancellationSignal, <executor>, new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() { @Override public void onResult(GetCredentialResponse result) { handleSignIn(result); } @Override public void onError(GetCredentialException e) { handleFailure(e); } } ); public void handleSignIn(GetCredentialResponse result) { // Handle the successfully returned credential. Credential credential = result.getCredential(); if (credential instanceof PublicKeyCredential) { String responseJson = ((PublicKeyCredential) credential).getAuthenticationResponseJson(); // Share responseJson i.e. a GetCredentialResponse on your server to validate and authenticate } else if (credential instanceof PasswordCredential) { String username = ((PasswordCredential) credential).getId(); String password = ((PasswordCredential) credential).getPassword(); // Use id and password to send to your server to validate and authenticate } else if (credential instanceof CustomCredential) { if (ExampleCustomCredential.TYPE.equals(credential.getType())) { try { ExampleCustomCredential customCred = ExampleCustomCredential.createFrom(customCredential.getData()); // Extract the required credentials and complete the // authentication as per the federated sign in or any external // sign in library flow } catch (ExampleCustomCredential.ExampleCustomCredentialParsingException e) { // Unlikely to happen. If it does, you likely need to update the // dependency version of your external sign-in library. Log.e(TAG, "Failed to parse an ExampleCustomCredential", e); } } else { // Catch any unrecognized custom credential type here. Log.e(TAG, "Unexpected type of credential"); } } else { // Catch any unrecognized credential type here. Log.e(TAG, "Unexpected type of credential"); } }
ตัวอย่างต่อไปนี้แสดงวิธีจัดรูปแบบคำขอ JSON เมื่อคุณได้รับพาสคีย์
{
"challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
"allowCredentials": [],
"timeout": 1800000,
"userVerification": "required",
"rpId": "credential-manager-app-test.glitch.me"
}
ตัวอย่างต่อไปนี้แสดงลักษณะของการตอบสนอง JSON หลังจากที่คุณได้รับ ข้อมูลเข้าสู่ระบบคีย์สาธารณะ:
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
"signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
"userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
}
}
จัดการข้อยกเว้นเมื่อไม่มีข้อมูลเข้าสู่ระบบ
ในบางกรณี ผู้ใช้อาจไม่มีข้อมูลเข้าสู่ระบบ หรือผู้ใช้อาจไม่ให้ความยินยอมในการใช้ข้อมูลเข้าสู่ระบบที่มีอยู่ หากเป็น getCredential()
มีการเรียกใช้ และไม่พบข้อมูลเข้าสู่ระบบ NoCredentialException
คือ
ส่งคืนแล้ว ในกรณีนี้ โค้ดของคุณควรรองรับ NoCredentialException
อินสแตนซ์
Kotlin
try {
val credential = credentialManager.getCredential(credentialRequest)
} catch (e: NoCredentialException) {
Log.e("CredentialManager", "No credential available", e)
}
Java
try {
Credential credential = credentialManager.getCredential(credentialRequest);
} catch (NoCredentialException e) {
Log.e("CredentialManager", "No credential available", e);
}
ใน Android 14 ขึ้นไป คุณสามารถลดเวลาในการตอบสนองเมื่อแสดงตัวเลือกบัญชีได้โดยใช้เมธอด prepareGetCredential()
ก่อนเรียกใช้ getCredential()
Kotlin
val response = credentialManager.prepareGetCredential(
GetCredentialRequest(
listOf(
<getPublicKeyCredentialOption>,
<getPasswordOption>
)
)
}
Java
GetCredentialResponse response = credentialManager.prepareGetCredential(
new GetCredentialRequest(
Arrays.asList(
new PublicKeyCredentialOption(),
new PasswordOption()
)
)
);
เมธอด prepareGetCredential()
จะไม่เรียกใช้องค์ประกอบ UI วิธีนี้ช่วย
คุณจะต้องเตรียมการ เพื่อให้สามารถ
เปิดใช้งานเนื้อหาที่เหลือในภายหลัง
การดำเนินการขอรับข้อมูลเข้าสู่ระบบ (ซึ่งเกี่ยวข้องกับ UI) ผ่าน getCredential()
API
ระบบจะแสดงข้อมูลที่แคชไว้ในออบเจ็กต์ PrepareGetCredentialResponse
ถ้า
มีข้อมูลเข้าสู่ระบบอยู่แล้ว ผลลัพธ์จะถูกแคชไว้ จากนั้นคุณจะ
เปิด getCredential()
API ที่เหลือในภายหลังเพื่อเรียกใช้บัญชี
พร้อมกับข้อมูลที่แคชไว้
ขั้นตอนการลงทะเบียน
คุณสามารถลงทะเบียนผู้ใช้เพื่อตรวจสอบสิทธิ์ได้โดยใช้พาสคีย์หรือรหัสผ่าน
สร้างพาสคีย์
หากต้องการให้ผู้ใช้มีตัวเลือกในการลงทะเบียนพาสคีย์และใช้เพื่อตรวจสอบสิทธิ์อีกครั้ง ให้ลงทะเบียนข้อมูลเข้าสู่ระบบของผู้ใช้โดยใช้ออบเจ็กต์ CreatePublicKeyCredentialRequest
Kotlin
fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) { val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest( // Contains the request in JSON format. Uses the standard WebAuthn // web JSON spec. requestJson = requestJson, // Defines whether you prefer to use only immediately available // credentials, not hybrid credentials, to fulfill this request. // This value is false by default. preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials, ) // Execute CreateCredentialRequest asynchronously to register credentials // for a user account. Handle success and failure cases with the result and // exceptions, respectively. coroutineScope.launch { try { val result = credentialManager.createCredential( // Use an activity-based context to avoid undefined system // UI launching behavior context = activityContext, request = createPublicKeyCredentialRequest, ) handlePasskeyRegistrationResult(result) } catch (e : CreateCredentialException){ handleFailure(e) } } } fun handleFailure(e: CreateCredentialException) { when (e) { is CreatePublicKeyCredentialDomException -> { // Handle the passkey DOM errors thrown according to the // WebAuthn spec. handlePasskeyError(e.domError) } is CreateCredentialCancellationException -> { // The user intentionally canceled the operation and chose not // to register the credential. } is CreateCredentialInterruptedException -> { // Retry-able error. Consider retrying the call. } is CreateCredentialProviderConfigurationException -> { // Your app is missing the provider configuration dependency. // Most likely, you're missing the // "credentials-play-services-auth" module. } is CreateCredentialUnknownException -> ... is CreateCredentialCustomException -> { // You have encountered an error from a 3rd-party SDK. If you // make the API call with a request object that's a subclass of // CreateCustomCredentialRequest using a 3rd-party SDK, then you // should check for any custom exception type constants within // that SDK to match with e.type. Otherwise, drop or log the // exception. } else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}") } }
Java
public void createPasskey(String requestJson, boolean preferImmediatelyAvailableCredentials) { CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest = // `requestJson` contains the request in JSON format. Uses the standard // WebAuthn web JSON spec. // `preferImmediatelyAvailableCredentials` defines whether you prefer // to only use immediately available credentials, not hybrid credentials, // to fulfill this request. This value is false by default. new CreatePublicKeyCredentialRequest( requestJson, preferImmediatelyAvailableCredentials); // Execute CreateCredentialRequest asynchronously to register credentials // for a user account. Handle success and failure cases with the result and // exceptions, respectively. credentialManager.createCredentialAsync( // Use an activity-based context to avoid undefined system // UI launching behavior requireActivity(), createPublicKeyCredentialRequest, cancellationSignal, executor, new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() { @Override public void onResult(CreateCredentialResponse result) { handleSuccessfulCreatePasskeyResult(result); } @Override public void onError(CreateCredentialException e) { if (e instanceof CreatePublicKeyCredentialDomException) { // Handle the passkey DOM errors thrown according to the // WebAuthn spec. handlePasskeyError(((CreatePublicKeyCredentialDomException)e).getDomError()); } else if (e instanceof CreateCredentialCancellationException) { // The user intentionally canceled the operation and chose not // to register the credential. } else if (e instanceof CreateCredentialInterruptedException) { // Retry-able error. Consider retrying the call. } else if (e instanceof CreateCredentialProviderConfigurationException) { // Your app is missing the provider configuration dependency. // Most likely, you're missing the // "credentials-play-services-auth" module. } else if (e instanceof CreateCredentialUnknownException) { } else if (e instanceof CreateCredentialCustomException) { // You have encountered an error from a 3rd-party SDK. If // you make the API call with a request object that's a // subclass of // CreateCustomCredentialRequest using a 3rd-party SDK, // then you should check for any custom exception type // constants within that SDK to match with e.type. // Otherwise, drop or log the exception. } else { Log.w(TAG, "Unexpected exception type " + e.getClass().getName()); } } } ); }
จัดรูปแบบคำขอ JSON
หลังจากสร้างพาสคีย์แล้ว คุณต้องเชื่อมโยงพาสคีย์กับบัญชีของผู้ใช้ และ จัดเก็บคีย์สาธารณะของพาสคีย์บนเซิร์ฟเวอร์ ตัวอย่างโค้ดต่อไปนี้แสดง วิธีจัดรูปแบบคำขอ JSON เมื่อคุณสร้างพาสคีย์
บล็อกโพสต์เกี่ยวกับการเปิดใช้งานการตรวจสอบสิทธิ์ที่ราบรื่นในแอปของคุณแสดงถึง วิธีจัดรูปแบบคำขอ JSON เมื่อสร้างพาสคีย์และ ตรวจสอบสิทธิ์โดยใช้พาสคีย์ และยังอธิบายสาเหตุที่รหัสผ่านไม่มีประสิทธิภาพ โซลูชันการตรวจสอบสิทธิ์, วิธีใช้ประโยชน์จากข้อมูลเข้าสู่ระบบไบโอเมตริกที่มีอยู่, วิธีการ เชื่อมโยงแอปกับเว็บไซต์ที่คุณเป็นเจ้าของ วิธีสร้างพาสคีย์ และวิธี เพื่อตรวจสอบสิทธิ์โดยใช้พาสคีย์
{
"challenge": "abc123",
"rp": {
"name": "Credential Manager example",
"id": "credential-manager-test.example.com"
},
"user": {
"id": "def456",
"name": "helloandroid@gmail.com",
"displayName": "helloandroid@gmail.com"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": 1800000,
"attestation": "none",
"excludeCredentials": [
{"id": "ghi789", "type": "public-key"},
{"id": "jkl012", "type": "public-key"}
],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
กำหนดค่าสำหรับ authenticatorAttachment
ตั้งค่าพารามิเตอร์ authenticatorAttachment
ได้ขณะสร้างข้อมูลเข้าสู่ระบบเท่านั้น
คุณสามารถระบุ platform
, cross-platform
หรือไม่มีค่าก็ได้ ในกรณีส่วนใหญ่ เราขอแนะนำให้ไม่ระบุค่า
platform
: หากต้องการลงทะเบียนอุปกรณ์ปัจจุบันของผู้ใช้หรือแจ้งให้ผู้ใช้ที่มีรหัสผ่านอัปเกรดเป็นพาสคีย์หลังจากลงชื่อเข้าใช้ ให้ตั้งค่าauthenticatorAttachment
เป็นplatform
cross-platform
: ค่านี้มักใช้เมื่อลงทะเบียนข้อมูลเข้าสู่ระบบแบบหลายปัจจัย และไม่ใช้ในบริบทของพาสคีย์- ไม่มีค่า: เพื่อให้ผู้ใช้มีความยืดหยุ่นในการสร้าง
ในอุปกรณ์ที่ต้องการ (เช่น ในการตั้งค่าบัญชี)
ไม่ควรระบุพารามิเตอร์
authenticatorAttachment
เมื่อผู้ใช้เลือก เพื่อเพิ่มพาสคีย์ ในกรณีส่วนใหญ่ การไม่ระบุพารามิเตอร์จะดีที่สุด ตัวเลือก
ป้องกันการสร้างพาสคีย์ที่ซ้ำกัน
แสดงรหัสข้อมูลเข้าสู่ระบบในอาร์เรย์ excludeCredentials
(ไม่บังคับ) เพื่อป้องกันการสร้างพาสคีย์ใหม่หากมีพาสคีย์ที่มีผู้ให้บริการพาสคีย์เดียวกันอยู่แล้ว
จัดการการตอบสนอง JSON
ข้อมูลโค้ดต่อไปนี้แสดงตัวอย่างการตอบกลับ JSON สำหรับการสร้างข้อมูลเข้าสู่ระบบด้วยคีย์สาธารณะ ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีจัดการคีย์สาธารณะที่ส่งคืน ข้อมูลเข้าสู่ระบบ
{
"id": "KEDetxZcUfinhVi6Za5nZQ",
"type": "public-key",
"rawId": "KEDetxZcUfinhVi6Za5nZQ",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
}
}
ยืนยันต้นทางจาก JSON ของข้อมูลไคลเอ็นต์
origin
หมายถึงแอปพลิเคชันหรือเว็บไซต์ที่
คำขอมาจากและใช้โดยพาสคีย์เพื่อป้องกันการโจมตีแบบฟิชชิง
เซิร์ฟเวอร์ของแอปต้องตรวจสอบแหล่งที่มาของข้อมูลไคลเอ็นต์กับรายการที่อนุญาตของแอปและเว็บไซต์ที่ได้รับอนุมัติ หากเซิร์ฟเวอร์
ได้รับคำขอจากแอปหรือเว็บไซต์จากต้นทางที่ไม่รู้จัก
คำขอควรถูกปฏิเสธ
ในกรณีของเว็บ origin
จะแสดงต้นทางในเว็บไซต์เดียวกันที่ลงชื่อเข้าใช้ข้อมูลเข้าสู่ระบบ ตัวอย่างเช่น หากระบุ URL
https://www.example.com:8443/store?category=shoes#athletic
origin
คือ
https://www.example.com:8443
สำหรับแอป Android นั้น User Agent จะตั้งค่า origin
เป็นลายเซ็นของ
แอปการโทร ลายเซ็นนี้ควรได้รับการยืนยันว่าตรงกับในเซิร์ฟเวอร์ของคุณกับ
ตรวจสอบผู้โทร API ของพาสคีย์ origin
ของ Android คือ URI ที่มาจากแฮช SHA-256 ของใบรับรองการรับรอง APK เช่น
android:apk-key-hash:<sha256_hash-of-apk-signing-cert>
คุณจะดูแฮช SHA-256 ของใบรับรองที่มีการรับรองจากคีย์สโตร์ได้โดย โดยเรียกใช้คำสั่งเทอร์มินัลต่อไปนี้
keytool -list -keystore <path-to-apk-signing-keystore>
แฮช SHA-256 อยู่ในรูปแบบเลขฐานสิบหกที่คั่นด้วยโคลอน (91:F7:CB:F9:D6:81…
) และค่า origin
ของ Android จะอยู่ในรูปแบบ Base64url
ตัวอย่าง Python นี้สาธิตวิธีแปลงรูปแบบแฮชเป็นรูปแบบที่เข้ากันได้
รูปแบบฐานสิบหกที่คั่นด้วยโคลอน:
import binascii
import base64
fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5'
print("android:apk-key-hash:" + base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))
แทนที่ค่าของ fingerprint
ด้วยค่าของคุณเอง ตัวอย่าง
ผลลัพธ์:
android:apk-key-hash:kffL-daBUxvHpY-4M8yhTavt5QnFEI2LsexohxrGPYU
จากนั้นคุณสามารถจับคู่สตริงนั้นเป็นแหล่งที่มาที่อนุญาตในเซิร์ฟเวอร์ได้ หากคุณมี ใบรับรองที่มีการรับรองหลายรายการ เช่น ใบรับรองสำหรับแก้ไขข้อบกพร่องและเปิดตัว หลายแอป จากนั้นทำตามขั้นตอนซ้ำและยอมรับต้นทางทั้งหมดเหล่านั้นว่าถูกต้อง บนเซิร์ฟเวอร์
บันทึกรหัสผ่านของผู้ใช้
หากผู้ใช้ระบุชื่อผู้ใช้และรหัสผ่านสำหรับขั้นตอนการตรวจสอบสิทธิ์ในแอป คุณจะลงทะเบียนข้อมูลเข้าสู่ระบบของผู้ใช้ที่สามารถใช้ตรวจสอบสิทธิ์ผู้ใช้ได้ โดยสร้างออบเจ็กต์ CreatePasswordRequest
ได้ดังนี้
Kotlin
fun registerPassword(username: String, password: String) { // Initialize a CreatePasswordRequest object. val createPasswordRequest = CreatePasswordRequest(id = username, password = password) // Create credential and handle result. coroutineScope.launch { try { val result = credentialManager.createCredential( // Use an activity based context to avoid undefined // system UI launching behavior. activityContext, createPasswordRequest ) handleRegisterPasswordResult(result) } catch (e: CreateCredentialException) { handleFailure(e) } } }
Java
void registerPassword(String username, String password) { // Initialize a CreatePasswordRequest object. CreatePasswordRequest createPasswordRequest = new CreatePasswordRequest(username, password); // Register the username and password. credentialManager.createCredentialAsync( // Use an activity-based context to avoid undefined // system UI launching behavior requireActivity(), createPasswordRequest, cancellationSignal, executor, new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() { @Override public void onResult(CreateCredentialResponse result) { handleResult(result); } @Override public void onError(CreateCredentialException e) { handleFailure(e); } } ); }
รองรับการกู้คืนข้อมูลเข้าสู่ระบบ
หากผู้ใช้ไม่มีสิทธิ์ในการเข้าถึงอุปกรณ์ที่เคยจัดเก็บไว้อีกต่อไป ข้อมูลเข้าสู่ระบบ ซึ่งผู้ใช้อาจต้องกู้คืนจากข้อมูลสำรองออนไลน์ที่ปลอดภัย หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีรองรับกระบวนการกู้คืนข้อมูลเข้าสู่ระบบนี้ โปรดอ่านส่วน "การกู้คืนสิทธิ์เข้าถึงหรือเพิ่มอุปกรณ์ใหม่" ในบล็อกโพสต์ความปลอดภัยของพาสคีย์ในเครื่องมือจัดการรหัสผ่านของ Google
เพิ่มการรองรับเครื่องมือจัดการรหัสผ่านที่มี URL ที่รู้จักของปลายทางพาสคีย์
เพื่อการผสานรวมที่ราบรื่นและเพื่อให้ใช้งานร่วมกับรหัสผ่านและข้อมูลเข้าสู่ระบบได้ในอนาคต สำหรับเครื่องมือการจัดการ เราขอแนะนำให้เพิ่มการรองรับปลายทางของพาสคีย์ที่รู้จักกันดี URL นี่คือโปรโตคอลแบบเปิดเพื่อให้ฝ่ายที่เกี่ยวข้องสามารถโฆษณาของตนอย่างเป็นทางการ รองรับพาสคีย์และให้ลิงก์โดยตรงสำหรับการลงทะเบียนพาสคีย์และ การจัดการ
- สําหรับบุคคลที่เชื่อถือที่
https://example.com
ซึ่งมีเว็บไซต์และแอป Android และ iOS URL ที่รู้จักกันดีจะเป็นhttps://example.com/.well-known/passkey-endpoints
เมื่อมีการค้นหา URL การตอบกลับควรใช้สคีมาต่อไปนี้
{ "enroll": "https://example.com/account/manage/passkeys/create" "manage": "https://example.com/account/manage/passkeys" }
หากต้องการให้ลิงก์นี้เปิดในแอปโดยตรงแทนที่จะเป็นในเว็บ ให้ใช้ลิงก์แอป Android
โปรดดูรายละเอียดเพิ่มเติมใน คำอธิบายURL ที่รู้จักกันดีของปลายทางพาสคีย์ใน GitHub
ช่วยให้ผู้ใช้จัดการพาสคีย์ด้วยการแสดงว่าผู้ให้บริการรายใดเป็นผู้สร้างพาสคีย์
ปัญหาหนึ่งที่ผู้ใช้พบเมื่อจัดการพาสคีย์หลายรายการที่เชื่อมโยงกับแอปหนึ่งๆ คือ การระบุพาสคีย์ที่ถูกต้องสำหรับการแก้ไขหรือลบ เพื่อช่วยแก้ปัญหานี้ เราขอแนะนำให้แอปและเว็บไซต์ระบุข้อมูลเพิ่มเติม เช่น ผู้ให้บริการที่สร้างข้อมูลเข้าสู่ระบบ วันที่สร้าง และวันที่ใช้ล่าสุดในรายการพาสคีย์ในหน้าจอการตั้งค่าของแอป โดยระบบจะดูข้อมูลผู้ให้บริการจาก AAGUID ที่เชื่อมโยงกับพาสคีย์ที่เกี่ยวข้อง AAGUID จะอยู่ในข้อมูลโปรแกรมตรวจสอบสิทธิ์ของพาสคีย์
เช่น หากผู้ใช้สร้างพาสคีย์ในอุปกรณ์ที่ขับเคลื่อนโดย Android โดยใช้ จากนั้น RP จะได้รับ AAGUID ซึ่งมีลักษณะดังนี้ ดังนี้: "ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4" ฝ่ายที่ต้องพึ่งพาสามารถ ใส่คำอธิบายประกอบในรายการพาสคีย์เพื่อระบุว่าพาสคีย์สร้างขึ้นโดยใช้ เครื่องมือจัดการรหัสผ่านบน Google
หากต้องการแมป AAGUID กับผู้ให้บริการพาสคีย์ RP สามารถใช้ชุมชนที่มาจากชุมชน AAGUID ค้นหา AAGUID ในรายการเพื่อดูชื่อและไอคอนของผู้ให้บริการพาสคีย์
อ่านเพิ่มเติมเกี่ยวกับการผสานรวม AAGUID
แก้ไขข้อผิดพลาดที่พบบ่อย
ตารางต่อไปนี้แสดงรหัสและคำอธิบายข้อผิดพลาดที่พบบ่อยหลายรายการ และ ให้ข้อมูลเกี่ยวกับสาเหตุ เช่น
รหัสและคำอธิบายข้อผิดพลาด | สาเหตุ |
---|---|
เมื่อเริ่มต้นลงชื่อเข้าใช้ไม่สำเร็จ: 16: ผู้โทรถูกบล็อกชั่วคราวเนื่องจากครบกำหนด กับข้อความแจ้งให้ลงชื่อเข้าใช้ที่ยกเลิกมากเกินไป | หากพบระยะเวลาพัก 24 ชั่วโมงนี้ระหว่างการพัฒนา คุณสามารถรีเซ็ตได้โดยล้างพื้นที่เก็บข้อมูลของแอปบริการ Google Play หรือหากต้องการเปิด/ปิดระยะเวลาพักนี้ในอุปกรณ์ทดสอบหรือโปรแกรมจำลอง ให้ไปที่แอปโทรศัพท์แล้วป้อนรหัสต่อไปนี้
|
ในการเริ่มลงชื่อเข้าใช้ไม่สำเร็จ: 8: ข้อผิดพลาดภายในที่ไม่รู้จัก |
|
CreatePublicKeyCredentialDomException: คำขอที่เข้ามาใหม่ไม่สามารถ ตรวจสอบแล้ว | รหัสแพ็กเกจของแอปไม่ได้ลงทะเบียนกับเซิร์ฟเวอร์ของคุณ ตรวจสอบความถูกต้องของข้อมูลนี้ ในการผสานรวมฝั่งเซิร์ฟเวอร์ |
CreateCredentialUnknownException: ระหว่างบันทึกรหัสผ่าน พบคำตอบว่าบันทึกรหัสผ่านไม่สำเร็จจาก One Tap 16: ข้ามการบันทึกรหัสผ่านเนื่องจากมีแนวโน้มว่าผู้ใช้จะได้รับข้อความแจ้งให้ป้อนข้อความอัตโนมัติของ Android | ข้อผิดพลาดนี้เกิดขึ้นเฉพาะใน Android 13 และต่ำกว่า และเฉพาะในกรณีที่ Google เป็นผู้ให้บริการการป้อนข้อความอัตโนมัติ ในกรณีนี้ ผู้ใช้จะเห็นการบันทึก จากการป้อนข้อความอัตโนมัติและบันทึกรหัสผ่านไว้ในรหัสผ่าน Google ผู้จัดการ โปรดทราบว่าระบบจะแชร์ข้อมูลเข้าสู่ระบบที่บันทึกไว้โดยใช้การป้อนข้อความอัตโนมัติด้วย Google กับ Credential Manager API ในแบบ 2 ทิศทาง ดังนั้น ข้อผิดพลาดนี้ ก็สามารถเพิกเฉยได้อย่างปลอดภัย |
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับ API เครื่องมือจัดการข้อมูลเข้าสู่ระบบและพาสคีย์ได้ที่ แหล่งข้อมูลต่อไปนี้
- คู่มือ UX ของพาสคีย์
- วิดีโอ: วิธีลดการพึ่งพารหัสผ่านในแอป Android ที่รองรับพาสคีย์
- Codelab: ดูวิธีลดความซับซ้อนของเส้นทางการตรวจสอบสิทธิ์โดยใช้ Credential Manager API ในแอป Android
- ตัวอย่างแอป: CredentialManager