Credential Manager หมายถึงชุด API ที่เปิดตัวใน Android 14 ซึ่งรองรับวิธีการลงชื่อเข้าใช้หลายวิธี เช่น ผู้ใช้และรหัสผ่าน พาสคีย์ และโซลูชันการลงชื่อเข้าใช้แบบรวมศูนย์ (เช่น ฟีเจอร์ลงชื่อเข้าใช้ด้วย Google) เมื่อเรียกใช้ Credential Manager API ระบบ Android จะรวบรวมข้อมูลเข้าสู่ระบบจากผู้ให้บริการข้อมูลเข้าสู่ระบบทั้งหมดที่ติดตั้งในอุปกรณ์ เอกสารนี้อธิบายชุด API ที่ให้บริการปลายทางการผสานรวมสําหรับผู้ให้บริการข้อมูลเข้าสู่ระบบเหล่านี้
ตั้งค่า
ก่อนใช้ฟังก์ชันการทำงานในผู้ให้บริการข้อมูลเข้าสู่ระบบ ให้ทำตามขั้นตอนการตั้งค่าที่แสดงในส่วนต่อไปนี้
ประกาศทรัพยากร Dependency
ในไฟล์ build.gradle
ของโมดูล ให้ประกาศทรัพยากร Dependency โดยใช้ไลบรารีเครื่องมือจัดการข้อมูลเข้าสู่ระบบเวอร์ชันล่าสุด ดังนี้
implementation "androidx.credentials:credentials:1.2.0-{latest}"
ประกาศองค์ประกอบบริการในไฟล์ Manifest
ในไฟล์ Manifest AndroidManifest.xml
ของแอป ให้ใส่<service>
การประกาศสำหรับคลาสบริการที่ขยายคลาส CredentialProviderService
จากไลบรารี androidx.credentials ดังที่แสดงในตัวอย่างด้านล่าง
<service android:name=".MyCredentialProviderService"
android:enabled="true"
android:exported="true"
android:label="My Credential Provider"
android:icon="<any drawable icon>"
android:permission="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE">
<intent-filter>
<action android:name="android.service.credentials.CredentialProviderService"/>
</intent-filter>
<meta-data
android:name="android.credentials.provider"
android:resource="@xml/provider"/>
</service>
สิทธิ์และตัวกรอง Intent ที่แสดงด้านบนเป็นส่วนสําคัญของขั้นตอน Credential Manager เพื่อให้ทํางานได้ตามที่คาดไว้ สิทธิ์นี้จําเป็นเพื่อให้มีเพียงระบบ Android เท่านั้นที่ลิงก์กับบริการนี้ได้ ระบบจะใช้ตัวกรอง Intent เพื่อค้นพบบริการนี้ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบที่เครื่องมือจัดการข้อมูลเข้าสู่ระบบจะใช้
ประกาศประเภทข้อมูลเข้าสู่ระบบที่รองรับ
ในไดเรกทอรี res/xml
ให้สร้างไฟล์ใหม่ชื่อ provider.xml
ในไฟล์นี้ ให้ประกาศประเภทข้อมูลเข้าสู่ระบบที่บริการของคุณรองรับผ่านค่าคงที่ที่กําหนดไว้สําหรับข้อมูลเข้าสู่ระบบแต่ละประเภทในไลบรารี ในตัวอย่างนี้ บริการรองรับรหัสผ่านแบบดั้งเดิมและพาสคีย์ โดยค่าคงที่ของพาสคีย์และรหัสผ่านจะกำหนดเป็น TYPE_PASSWORD_CREDENTIAL
และ TYPE_PUBLIC_KEY_CREDENTIAL
<?xml version="1.0" encoding="utf-8"?>
<credential-provider xmlns:android="http://schemas.android.com/apk/res/android">
<capabilities>
<capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
<capability name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>
ใน API ระดับก่อนหน้า ผู้ให้บริการข้อมูลเข้าสู่ระบบจะผสานรวมกับ API เช่น การป้อนรหัสผ่านและข้อมูลอื่นๆ โดยอัตโนมัติ ผู้ให้บริการเหล่านี้สามารถใช้โครงสร้างพื้นฐานภายในเดียวกันเพื่อจัดเก็บข้อมูลเข้าสู่ระบบประเภทที่มีอยู่ ในขณะที่ขยายการให้บริการเพื่อรองรับข้อมูลเข้าสู่ระบบประเภทอื่นๆ ซึ่งรวมถึงพาสคีย์
แนวทางแบบ 2 ระยะในการโต้ตอบกับผู้ให้บริการ
เครื่องมือจัดการข้อมูลเข้าสู่ระบบจะโต้ตอบกับผู้ให้บริการข้อมูลเข้าสู่ระบบใน 2 ระยะ ดังนี้
- ระยะแรกคือระยะเริ่มต้น/การค้นหา ซึ่งระบบจะเชื่อมโยงกับบริการของผู้ให้บริการข้อมูลเข้าสู่ระบบและเรียกใช้เมธอด
onBeginGetCredentialRequest()
,onBeginCreateCredentialRequest()
หรือonClearCredentialStateRequest()
ด้วยคำขอBegin…
ผู้ให้บริการต้องประมวลผลคำขอเหล่านี้และตอบกลับด้วยคําตอบBegin…
โดยป้อนข้อมูลที่มีรายการที่แสดงถึงตัวเลือกภาพที่จะแสดงในตัวเลือกบัญชี แต่ละรายการต้องมีPendingIntent
ที่กำหนดไว้ - เมื่อผู้ใช้เลือกรายการหนึ่ง ระยะการเลือกจะเริ่มขึ้น และระบบจะเรียกใช้
PendingIntent
ที่เชื่อมโยงกับรายการนั้น ซึ่งจะแสดงกิจกรรมของผู้ให้บริการที่เกี่ยวข้อง เมื่อผู้ใช้โต้ตอบกับกิจกรรมนี้เสร็จแล้ว ผู้ให้บริการข้อมูลเข้าสู่ระบบต้องตั้งค่าการตอบกลับผลลัพธ์ของกิจกรรมก่อนที่จะสิ้นสุดกิจกรรม จากนั้นระบบจะส่งการตอบกลับนี้ไปยังแอปไคลเอ็นต์ที่เรียกใช้เครื่องมือจัดการข้อมูลเข้าสู่ระบบ
จัดการการสร้างพาสคีย์
จัดการการค้นหาการสร้างพาสคีย์
เมื่อแอปไคลเอ็นต์ต้องการสร้างพาสคีย์และจัดเก็บไว้กับผู้ให้บริการข้อมูลเข้าสู่ระบบ แอปจะเรียกใช้ createCredential
API หากต้องการจัดการคำขอนี้ในบริการของผู้ให้บริการข้อมูลเข้าสู่ระบบเพื่อให้ระบบจัดเก็บพาสคีย์ไว้ในพื้นที่เก็บข้อมูลของคุณจริงๆ ให้ทำตามขั้นตอนที่แสดงในส่วนต่อไปนี้
- ลบล้างเมธอด
onBeginCreateCredentialRequest()
ในบริการของคุณซึ่งขยายมาจากCredentialProviderService
- จัดการ
BeginCreateCredentialRequest
โดยสร้างBeginCreateCredentialResponse
ที่เกี่ยวข้องและส่งผ่านผ่านการเรียกกลับ - ขณะสร้าง
BeginCreateCredentialResponse
ให้เพิ่มCreateEntries
ที่จําเป็นCreateEntry
แต่ละรายการควรสอดคล้องกับบัญชีที่บันทึกข้อมูลเข้าสู่ระบบได้ และต้องตั้งค่าPendingIntent
พร้อมกับข้อมูลเมตาอื่นๆ ที่จำเป็น
ตัวอย่างต่อไปนี้แสดงวิธีใช้ขั้นตอนเหล่านี้
override fun onBeginCreateCredentialRequest(
request: BeginCreateCredentialRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException>,
) {
val response: BeginCreateCredentialResponse? = processCreateCredentialRequest(request)
if (response != null) {
callback.onResult(response)
} else {
callback.onError(CreateCredentialUnknownException())
}
}
fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse? {
when (request) {
is BeginCreatePublicKeyCredentialRequest -> {
// Request is passkey type
return handleCreatePasskeyQuery(request)
}
}
// Request not supported
return null
}
private fun handleCreatePasskeyQuery(
request: BeginCreatePublicKeyCredentialRequest
): BeginCreateCredentialResponse {
// Adding two create entries - one for storing credentials to the 'Personal'
// account, and one for storing them to the 'Family' account. These
// accounts are local to this sample app only.
val createEntries: MutableList<CreateEntry> = mutableListOf()
createEntries.add( CreateEntry(
PERSONAL_ACCOUNT_ID,
createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
))
createEntries.add( CreateEntry(
FAMILY_ACCOUNT_ID,
createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSKEY_INTENT)
))
return BeginCreateCredentialResponse(createEntries)
}
private fun createNewPendingIntent(accountId: String, action: String): PendingIntent {
val intent = Intent(action).setPackage(PACKAGE_NAME)
// Add your local account ID as an extra to the intent, so that when
// user selects this entry, the credential can be saved to this
// account
intent.putExtra(EXTRA_KEY_ACCOUNT_ID, accountId)
return PendingIntent.getActivity(
applicationContext, UNIQUE_REQ_CODE,
intent, (
PendingIntent.FLAG_MUTABLE
or PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
โครงสร้าง PendingIntent
ควรเป็นไปตามข้อกำหนดต่อไปนี้
- ควรตั้งค่ากิจกรรมที่เกี่ยวข้องให้แสดงข้อความแจ้ง ยืนยัน หรือการเลือกข้อมูลไบโอเมตริกที่จำเป็น
- ข้อมูลที่จำเป็นซึ่งผู้ให้บริการต้องการเมื่อมีการเรียกใช้กิจกรรมที่เกี่ยวข้องควรตั้งค่าเป็นข้อมูลเพิ่มเติมใน Intent ที่ใช้สร้าง
PendingIntent
เช่นaccountId
ในขั้นตอนการสร้าง PendingIntent
ต้องสร้างขึ้นโดยใช้ FlagPendingIntent.FLAG_MUTABLE
เพื่อให้ระบบเพิ่มคำขอสุดท้ายต่อท้าย Intent Extra ได้PendingIntent
ต้องไม่สร้างโดยใช้ FlagPendingIntent.FLAG_ONE_SHOT
เนื่องจากผู้ใช้อาจเลือกรายการหนึ่ง ย้อนกลับ แล้วเลือกรายการนั้นอีกครั้ง ซึ่งจะทำให้PendingIntent
ทำงาน 2 ครั้งPendingIntent
ต้องสร้างขึ้นด้วยรหัสคำขอที่ไม่ซ้ำกันเพื่อให้แต่ละรายการมีPendingIntent
ที่เกี่ยวข้องเป็นของตนเอง
จัดการการเลือกรายการสำหรับคำขอสร้างพาสคีย์
- เมื่อผู้ใช้เลือก
CreateEntry
ที่มีการป้อนข้อมูลก่อนหน้านี้ ระบบจะเรียกใช้PendingIntent
ที่เกี่ยวข้องและสร้างผู้ให้บริการที่เกี่ยวข้องActivity
- หลังจากเรียกใช้เมธอด
onCreate
ของกิจกรรมแล้ว ให้เข้าถึง Intent ที่เชื่อมโยงและส่งไปยังคลาสPendingIntentHander
เพื่อรับProviderCreateCredentialRequest
- ดึง
requestJson
,callingAppInfo
และclientDataHash
ออกจากคำขอ - ดึง
accountId
ในพื้นที่ออกจากข้อมูลเพิ่มเติมของ Intent นี่เป็นตัวอย่างการใช้งานเฉพาะแอปและไม่จำเป็น คุณใช้รหัสบัญชีนี้เพื่อจัดเก็บข้อมูลเข้าสู่ระบบนี้กับรหัสบัญชีนี้ได้ - ตรวจสอบ
requestJson
ตัวอย่างด้านล่างใช้คลาสข้อมูลในเครื่อง เช่นPublicKeyCredentialCreationOptions
เพื่อแปลง JSON อินพุตเป็นคลาสที่มีโครงสร้างตามข้อกำหนดของ WebAuthn ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถแทนที่การดำเนินการนี้ด้วยโปรแกรมแยกวิเคราะห์ของคุณเองได้ - ตรวจสอบลิงก์ชิ้นงานสำหรับแอปการโทรหากการโทรมาจากแอป Android ที่มาพร้อมเครื่อง
- แสดงข้อความแจ้งให้ตรวจสอบสิทธิ์ ตัวอย่างด้านล่างใช้ Android Biometric API
- เมื่อตรวจสอบสิทธิ์สำเร็จแล้ว ให้สร้าง
credentialId
และคู่คีย์ - บันทึกคีย์ส่วนตัวในฐานข้อมูลเครื่องเพื่อใช้กับ
callingAppInfo.packageName
- สร้างการตอบกลับ JSON ของ Web Authentication API ที่ประกอบด้วยคีย์สาธารณะและ
credentialId
ตัวอย่างด้านล่างใช้คลาสยูทิลิตีในเครื่อง เช่นAuthenticatorAttestationResponse
และFidoPublicKeyCredential
ที่ช่วยสร้าง JSON ตามข้อกำหนดที่กล่าวถึงก่อนหน้านี้ ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถแทนที่คลาสเหล่านี้ด้วยเครื่องมือสร้างของคุณเองได้ - สร้าง
CreatePublicKeyCredentialResponse
ด้วย JSON ที่สร้างขึ้นด้านบน - ตั้ง
CreatePublicKeyCredentialResponse
เป็นส่วนเสริมในIntent
ถึงPendingIntentHander.setCreateCredentialResponse()
และตั้งความตั้งใจนั้นเป็นผลของกิจกรรม - ทำกิจกรรมให้เสร็จ
ตัวอย่างโค้ดด้านล่างแสดงขั้นตอนเหล่านี้ โค้ดนี้ต้องได้รับการจัดการในคลาส Activity เมื่อมีการเรียกใช้ onCreate()
val request =
PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
if (request != null && request.callingRequest is CreatePublicKeyCredentialRequest) {
val publicKeyRequest: CreatePublicKeyCredentialRequest =
request.callingRequest as CreatePublicKeyCredentialRequest
createPasskey(
publicKeyRequest.requestJson,
request.callingAppInfo,
publicKeyRequest.clientDataHash,
accountId
)
}
fun createPasskey(
requestJson: String,
callingAppInfo: CallingAppInfo?,
clientDataHash: ByteArray?,
accountId: String?
) {
val request = PublicKeyCredentialCreationOptions(requestJson)
val biometricPrompt = BiometricPrompt(
this,
<executor>,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int, errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
// Generate a credentialId
val credentialId = ByteArray(32)
SecureRandom().nextBytes(credentialId)
// Generate a credential key pair
val spec = ECGenParameterSpec("secp256r1")
val keyPairGen = KeyPairGenerator.getInstance("EC");
keyPairGen.initialize(spec)
val keyPair = keyPairGen.genKeyPair()
// Save passkey in your database as per your own implementation
// Create AuthenticatorAttestationResponse object to pass to
// FidoPublicKeyCredential
val response = AuthenticatorAttestationResponse(
requestOptions = request,
credentialId = credentialId,
credentialPublicKey = getPublicKeyFromKeyPair(keyPair),
origin = appInfoToOrigin(callingAppInfo),
up = true,
uv = true,
be = true,
bs = true,
packageName = callingAppInfo.packageName
)
val credential = FidoPublicKeyCredential(
rawId = credentialId, response = response
)
val result = Intent()
val createPublicKeyCredResponse =
CreatePublicKeyCredentialResponse(credential.json())
// Set the CreateCredentialResponse as the result of the Activity
PendingIntentHandler.setCreateCredentialResponse(
result, createPublicKeyCredResponse
)
setResult(Activity.RESULT_OK, result)
finish()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Use your screen lock")
.setSubtitle("Create passkey for ${request.rp.name}")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
/* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
)
.build()
biometricPrompt.authenticate(promptInfo)
}
fun appInfoToOrigin(info: CallingAppInfo): String {
val cert = info.signingInfo.apkContentsSigners[0].toByteArray()
val md = MessageDigest.getInstance("SHA-256");
val certHash = md.digest(cert)
// This is the format for origin
return "android:apk-key-hash:${b64Encode(certHash)}"
}
จัดการการค้นหาคำขอสร้างรหัสผ่าน
ในการจัดการข้อความค้นหาสำหรับคำขอสร้างรหัสผ่าน ให้ทำดังนี้
- ในเมธอด
processCreateCredentialRequest()
ที่กล่าวถึงในส่วนก่อนหน้า ให้เพิ่มอีกเคสหนึ่งในบล็อกสวิตช์สำหรับการจัดการคำขอรหัสผ่าน - ขณะสร้าง
BeginCreateCredentialResponse
ให้เพิ่มCreateEntries
ที่จำเป็น CreateEntry
แต่ละรายการควรตรงกับบัญชีที่บันทึกข้อมูลเข้าสู่ระบบได้ และต้องตั้งค่าPendingIntent
พร้อมกับข้อมูลเมตาอื่นๆ
ตัวอย่างต่อไปนี้แสดงวิธีใช้ขั้นตอนเหล่านี้
fun processCreateCredentialRequest(
request: BeginCreateCredentialRequest
): BeginCreateCredentialResponse? {
when (request) {
is BeginCreatePublicKeyCredentialRequest -> {
// Request is passkey type
return handleCreatePasskeyQuery(request)
}
is BeginCreatePasswordCredentialRequest -> {
// Request is password type
return handleCreatePasswordQuery(request)
}
}
return null
}
private fun handleCreatePasswordQuery(
request: BeginCreatePasswordCredentialRequest
): BeginCreateCredentialResponse {
val createEntries: MutableList<CreateEntry> = mutableListOf()
// Adding two create entries - one for storing credentials to the 'Personal'
// account, and one for storing them to the 'Family' account. These
// accounts are local to this sample app only.
createEntries.add(
CreateEntry(
PERSONAL_ACCOUNT_ID,
createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
)
)
createEntries.add(
CreateEntry(
FAMILY_ACCOUNT_ID,
createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSWORD_INTENT)
)
)
return BeginCreateCredentialResponse(createEntries)
}
จัดการการเลือกรายการสำหรับคำขอสร้างรหัสผ่าน
เมื่อผู้ใช้เลือก CreateEntry
ที่มีการป้อนข้อมูล PendingIntent
ที่เกี่ยวข้องจะดำเนินการและเรียกกิจกรรมที่เกี่ยวข้องขึ้นมา เข้าถึง Intent ที่เชื่อมโยงซึ่งส่งมาใน onCreate
และส่งไปยังคลาส PendingIntentHander
เพื่อรับเมธอด ProviderCreateCredentialRequest
ตัวอย่างด้านล่างแสดงวิธีใช้กระบวนการนี้ รหัสนี้ต้องได้รับการจัดการในเมธอด onCreate()
ของกิจกรรม
val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent)
val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID)
val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest
// Fetch the ID and password from the request and save it in your database
<your_database>.addNewPassword(
PasswordInfo(
request.id,
request.password,
createRequest.callingAppInfo.packageName
)
)
//Set the final response back
val result = Intent()
val response = CreatePasswordResponse()
PendingIntentHandler.setCreateCredentialResponse(result, response)
setResult(Activity.RESULT_OK, result)
this@<activity>.finish()
จัดการการลงชื่อเข้าใช้ของผู้ใช้
การจัดการการลงชื่อเข้าใช้ของผู้ใช้จะดำเนินการตามขั้นตอนต่อไปนี้
- เมื่อแอปไคลเอ็นต์พยายามลงชื่อเข้าใช้ผู้ใช้ แอปจะเตรียมอินสแตนซ์
GetCredentialRequest
- เฟรมเวิร์ก Android จะส่งต่อคําขอนี้ไปยังผู้ให้บริการข้อมูลเข้าสู่ระบบที่เกี่ยวข้องทั้งหมดโดยการเชื่อมโยงกับบริการเหล่านี้
- จากนั้นบริการของผู้ให้บริการจะได้รับ
BeginGetCredentialRequest
ที่มีรายการBeginGetCredentialOption
โดยแต่ละรายการมีพารามิเตอร์ที่ใช้ในการดึงข้อมูลเข้าสู่ระบบที่ตรงกันได้
หากต้องการจัดการคําขอนี้ในบริการของผู้ให้บริการข้อมูลเข้าสู่ระบบ ให้ทําตามขั้นตอนต่อไปนี้
ลบล้างเมธอด
onBeginGetCredentialRequest()
เพื่อจัดการคําขอ โปรดทราบว่าหากข้อมูลเข้าสู่ระบบล็อกอยู่ คุณจะตั้งค่าAuthenticationAction
ในคำตอบและเรียกใช้ Callback ได้ทันทีprivate val unlockEntryTitle = "Authenticate to continue" override fun onBeginGetCredentialRequest( request: BeginGetCredentialRequest, cancellationSignal: CancellationSignal, callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>, ) { if (isAppLocked()) { callback.onResult(BeginGetCredentialResponse( authenticationActions = mutableListOf(AuthenticationAction( unlockEntryTitle, createUnlockPendingIntent()) ) ) ) return } try { response = processGetCredentialRequest(request) callback.onResult(response) } catch (e: GetCredentialException) { callback.onError(GetCredentialUnknownException()) } }
ผู้ให้บริการที่ต้องมีการปลดล็อกข้อมูลเข้าสู่ระบบก่อนที่จะแสดง
credentialEntries
ใดๆ จะต้องตั้งค่า Intent ที่รอดำเนินการซึ่งนำผู้ใช้ไปยังขั้นตอนการปลดล็อกของแอป โดยทำดังนี้private fun createUnlockPendingIntent(): PendingIntent { val intent = Intent(UNLOCK_INTENT).setPackage(PACKAGE_NAME) return PendingIntent.getActivity( applicationContext, UNIQUE_REQUEST_CODE, intent, ( PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) ) }
เรียกข้อมูลเข้าสู่ระบบจากฐานข้อมูลในเครื่องและตั้งค่าโดยใช้
CredentialEntries
เพื่อแสดงในตัวเลือก สำหรับพาสคีย์ คุณสามารถตั้งค่าcredentialId
เป็นข้อมูลเพิ่มเติมใน Intent เพื่อให้ทราบว่าพาสคีย์นั้นแมปกับข้อมูลเข้าสู่ระบบใดเมื่อผู้ใช้เลือกรายการนี้companion object { // These intent actions are specified for corresponding activities // that are to be invoked through the PendingIntent(s) private const val GET_PASSKEY_INTENT_ACTION = "PACKAGE_NAME.GET_PASSKEY" private const val GET_PASSWORD_INTENT_ACTION = "PACKAGE_NAME.GET_PASSWORD" } fun processGetCredentialsRequest( request: BeginGetCredentialRequest ): BeginGetCredentialResponse { val callingPackage = request.callingAppInfo?.packageName val credentialEntries: MutableList<CredentialEntry> = mutableListOf() for (option in request.beginGetCredentialOptions) { when (option) { is BeginGetPasswordOption -> { credentialEntries.addAll( populatePasswordData( callingPackage, option ) ) } is BeginGetPublicKeyCredentialOption -> { credentialEntries.addAll( populatePasskeyData( callingPackage, option ) ) ) } else -> { Log.i(TAG, "Request not supported") } } } return BeginGetCredentialResponse(credentialEntries) }
ค้นหาข้อมูลเข้าสู่ระบบจากฐานข้อมูล แล้วสร้างรายการพาสคีย์และรหัสผ่านเพื่อป้อนข้อมูล
private fun populatePasskeyData( callingAppInfo: CallingAppInfo, option: BeginGetPublicKeyCredentialOption ): List<CredentialEntry> { val passkeyEntries: MutableList<CredentialEntry> = mutableListOf() val request = PublicKeyCredentialRequestOptions(option.requestJson) // Get your credentials from database where you saved during creation flow val creds = <getCredentialsFromInternalDb(request.rpId)> val passkeys = creds.passkeys for (passkey in passkeys) { val data = Bundle() data.putString("credId", passkey.credId) passkeyEntries.add( PublicKeyCredentialEntry( context = applicationContext, username = passkey.username, pendingIntent = createNewPendingIntent( GET_PASSKEY_INTENT_ACTION, data ), beginPublicKeyCredentialOption = option, displayName = passkey.displayName, icon = passkey.icon ) ) } return passkeyEntries } // Fetch password credentials and create password entries to populate to // the user private fun populatePasswordData( callingPackage: String, option: BeginGetPasswordOption ): List<CredentialEntry> { val passwordEntries: MutableList<CredentialEntry> = mutableListOf() // Get your password credentials from database where you saved during // creation flow val creds = <getCredentialsFromInternalDb(callingPackage)> val passwords = creds.passwords for (password in passwords) { passwordEntries.add( PasswordCredentialEntry( context = applicationContext, username = password.username, pendingIntent = createNewPendingIntent( GET_PASSWORD_INTENT ), beginGetPasswordOption = option displayName = password.username, icon = password.icon ) ) } return passwordEntries } private fun createNewPendingIntent( action: String, extra: Bundle? = null ): PendingIntent { val intent = Intent(action).setPackage(PACKAGE_NAME) if (extra != null) { intent.putExtra("CREDENTIAL_DATA", extra) } return PendingIntent.getActivity( applicationContext, UNIQUE_REQUEST_CODE, intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) ) }
เมื่อค้นหาและป้อนข้อมูลเข้าสู่ระบบแล้ว คุณจะต้องจัดการขั้นตอนการเลือกข้อมูลเข้าสู่ระบบที่ผู้ใช้เลือก ไม่ว่าจะเป็นพาสคีย์หรือรหัสผ่าน
การจัดการการเลือกพาสคีย์ของผู้ใช้
- ในเมธอด
onCreate
ของกิจกรรมที่เกี่ยวข้อง ให้เรียกข้อมูล Intent ที่เชื่อมโยง แล้วส่งไปยังPendingIntentHandler.retrieveProviderGetCredentialRequest()
- ดึงข้อมูล
GetPublicKeyCredentialOption
จากคำขอที่ดึงข้อมูลไว้ด้านบน จากนั้นดึงข้อมูลrequestJson
และclientDataHash
จากตัวเลือกนี้ - ดึงข้อมูล
credentialId
จาก Intent Extra ซึ่งผู้ให้บริการข้อมูลเข้าสู่ระบบสร้างขึ้นเมื่อตั้งค่าPendingIntent
ที่เกี่ยวข้อง - ดึงข้อมูลพาสคีย์จากฐานข้อมูลในเครื่องโดยใช้พารามิเตอร์คำขอที่เข้าถึงได้ด้านบน
ยืนยันว่าพาสคีย์ถูกต้องด้วยข้อมูลเมตาที่ดึงมาและการยืนยันผู้ใช้
val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val publicKeyRequest = getRequest.credentialOption as GetPublicKeyCredentialOption val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA") val credIdEnc = requestInfo.getString("credId") // Get the saved passkey from your database based on the credential ID // from the publickeyRequest val passkey = <your database>.getPasskey(credIdEnc) // Decode the credential ID, private key and user ID val credId = b64Decode(credIdEnc) val privateKey = b64Decode(passkey.credPrivateKey) val uid = b64Decode(passkey.uid) val origin = appInfoToOrigin(getRequest.callingAppInfo) val packageName = getRequest.callingAppInfo.packageName validatePasskey( publicKeyRequest.requestJson, origin, packageName, uid, passkey.username, credId, privateKey )
หากต้องการตรวจสอบผู้ใช้ ให้แสดงข้อความแจ้งข้อมูลไบโอเมตริก (หรือวิธีการยืนยันอื่นๆ) ข้อมูลโค้ดด้านล่างใช้ Android Biometric API
เมื่อการตรวจสอบสิทธิ์สําเร็จแล้ว ให้สร้างการตอบกลับ JSON ตามข้อกําหนด W3 เกี่ยวกับการยืนยันผ่านเว็บ ในตัวข้อมูลโค้ดด้านล่างนี้ คลาสข้อมูลตัวช่วย เช่น
AuthenticatorAssertionResponse
จะใช้เพื่อรับพารามิเตอร์ที่มีโครงสร้างและแปลงเป็นรูปแบบ JSON ที่จําเป็น การตอบกลับมีลายเซ็นดิจิทัลจากคีย์ส่วนตัวของข้อมูลเข้าสู่ระบบ WebAuthn เซิร์ฟเวอร์ของบุคคลที่เชื่อถือสามารถยืนยันลายเซ็นนี้เพื่อตรวจสอบสิทธิ์ผู้ใช้ก่อนลงชื่อเข้าใช้ได้สร้าง
PublicKeyCredential
โดยใช้ JSON ที่สร้างขึ้นด้านบนและตั้งค่าเป็นGetCredentialResponse
สุดท้าย ตั้งค่าคำตอบสุดท้ายนี้เกี่ยวกับผลลัพธ์ของกิจกรรมนี้
ตัวอย่างต่อไปนี้แสดงวิธีใช้ขั้นตอนเหล่านี้
val request = PublicKeyCredentialRequestOptions(requestJson)
val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes)
val biometricPrompt = BiometricPrompt(
this,
<executor>,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(
errorCode: Int, errString: CharSequence
) {
super.onAuthenticationError(errorCode, errString)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
finish()
}
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(result)
val response = AuthenticatorAssertionResponse(
requestOptions = request,
credentialId = credId,
origin = origin,
up = true,
uv = true,
be = true,
bs = true,
userHandle = uid,
packageName = packageName
)
val sig = Signature.getInstance("SHA256withECDSA");
sig.initSign(privateKey)
sig.update(response.dataToSign())
response.signature = sig.sign()
val credential = FidoPublicKeyCredential(
rawId = credId, response = response
)
val result = Intent()
val passkeyCredential = PublicKeyCredential(credential.json)
PendingIntentHandler.setGetCredentialResponse(
result, GetCredentialResponse(passkeyCredential)
)
setResult(RESULT_OK, result)
finish()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Use your screen lock")
.setSubtitle("Use passkey for ${request.rpId}")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
/* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */
)
.build()
biometricPrompt.authenticate(promptInfo)
การจัดการการเลือกของผู้ใช้สําหรับการตรวจสอบสิทธิ์ด้วยรหัสผ่าน
- ในกิจกรรมที่เกี่ยวข้อง ให้เข้าถึง Intent ที่ส่งไปยัง
onCreate
และดึงข้อมูลProviderGetCredentialRequest
โดยใช้PendingIntentHandler
ใช้
GetPasswordOption
ในคำขอเพื่อเรียกข้อมูลเข้าสู่ระบบรหัสผ่านสำหรับชื่อแพ็กเกจขาเข้าval getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) val passwordOption = getRequest.credentialOption as GetPasswordCredentialOption val username = passwordOption.username // Fetch the credentials for the calling app package name val creds = <your_database>.getCredentials(callingAppInfo.packageName) val passwords = creds.passwords val it = passwords.iterator() var password = "" while (it.hasNext() == true) { val passwordItemCurrent = it.next() if (passwordItemCurrent.username == username) { password = passwordItemCurrent.password break } }
เมื่อดึงข้อมูลแล้ว ให้ตั้งค่าการตอบกลับสำหรับข้อมูลเข้าสู่ระบบของรหัสผ่านที่เลือก
// Set the response back val result = Intent() val passwordCredential = PasswordCredential(username, password) PendingIntentHandler.setGetCredentialResponse( result, GetCredentialResponse(passwordCredential) ) setResult(Activity.RESULT_OK, result) finish()
จัดการการเลือกรายการการดำเนินการตรวจสอบสิทธิ์
ตามที่กล่าวไว้ก่อนหน้านี้ ผู้ให้บริการข้อมูลเข้าสู่ระบบตั้งค่า AuthenticationAction
ได้หากข้อมูลเข้าสู่ระบบล็อกอยู่ หากผู้ใช้เลือกรายการนี้ ระบบจะเรียกใช้กิจกรรมที่สอดคล้องกับชุดการดำเนินการ Intent ใน PendingIntent
จากนั้นผู้ให้บริการข้อมูลเข้าสู่ระบบจะแสดงขั้นตอนการตรวจสอบสิทธิ์ด้วยข้อมูลไบโอเมตริกหรือกลไกที่คล้ายกันเพื่อปลดล็อกข้อมูลเข้าสู่ระบบได้ หากดำเนินการสำเร็จ ผู้ให้บริการข้อมูลเข้าสู่ระบบต้องสร้าง BeginGetCredentialResponse
ซึ่งคล้ายกับวิธีจัดการการลงชื่อเข้าใช้ของผู้ใช้ตามที่อธิบายไว้ข้างต้น เนื่องจากตอนนี้ข้อมูลเข้าสู่ระบบได้ปลดล็อกแล้ว จากนั้นต้องตั้งค่าการตอบกลับนี้ผ่านเมธอด PendingIntentHandler.setBeginGetCredentialResponse()
ก่อนจึงจะตั้งค่า Intent ที่เตรียมไว้เป็นผลการค้นหาและกิจกรรมจะเสร็จสมบูรณ์
ล้างคำขอข้อมูลเข้าสู่ระบบ
แอปไคลเอ็นต์อาจขอให้ล้างสถานะที่เก็บไว้สำหรับการเลือกข้อมูลเข้าสู่ระบบ เช่น ผู้ให้บริการข้อมูลเข้าสู่ระบบอาจจำข้อมูลเข้าสู่ระบบที่เลือกไว้ก่อนหน้านี้และแสดงข้อมูลเข้าสู่ระบบนั้นครั้งถัดไปเท่านั้น แอปไคลเอ็นต์เรียกใช้ API นี้และคาดหวังว่าระบบจะล้างการเลือกที่ติดอยู่ บริการของผู้ให้บริการข้อมูลเข้าสู่ระบบจะจัดการคำขอนี้ได้โดยลบล้างเมธอด onClearCredentialStateRequest()
ดังนี้
override fun onClearCredentialStateRequest(
request: android.service.credentials.ClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>,
) {
// Delete any maintained state as appropriate.
}
เพิ่มความสามารถในการลิงก์ไปยังหน้าการตั้งค่าของผู้ให้บริการ
หากต้องการอนุญาตให้ผู้ใช้เปิดการตั้งค่าของผู้ให้บริการจากหน้าจอรหัสผ่าน พาสคีย์ และป้อนข้อความอัตโนมัติ แอปของผู้ให้บริการข้อมูลเข้าสู่ระบบควรใช้แอตทริบิวต์ไฟล์ Manifest ของ
credential-provider
settingsActivity
ใน
res/xml/provider.xml
แอตทริบิวต์นี้ช่วยให้คุณใช้ Intent เพื่อเปิดหน้าจอการตั้งค่าของแอปได้หากผู้ใช้คลิกชื่อผู้ให้บริการในรายการบริการรหัสผ่าน พาสคีย์ และการป้อนข้อความอัตโนมัติ ตั้งค่าของแอตทริบิวต์นี้เป็นชื่อของกิจกรรมที่จะเปิดจากหน้าจอการตั้งค่า
<credential-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsSubtitle="Example settings provider name"
android:settingsActivity="com.example.SettingsActivity">
<capabilities>
<capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
</capabilities>
</credential-provider>
ความตั้งใจในการตั้งค่า
เปิดการตั้งค่า: Intent android.settings.CREDENTIAL_PROVIDER
จะแสดงหน้าจอการตั้งค่าที่ผู้ใช้สามารถเลือกผู้ให้บริการข้อมูลเข้าสู่ระบบที่ต้องการและเพิ่มเติมได้
บริการข้อมูลเข้าสู่ระบบที่ต้องการ: Intent ACTION_REQUEST_SET_AUTOFILL_SERVICE
จะเปลี่ยนเส้นทางผู้ใช้ไปยังหน้าจอการเลือกผู้ให้บริการที่ต้องการ ผู้ให้บริการที่เลือกในหน้าจอนี้จะกลายเป็นข้อมูลเข้าสู่ระบบและผู้ให้บริการป้อนข้อความอัตโนมัติที่ต้องการ
รับรายการที่อนุญาตของแอปที่มีสิทธิ์
แอปที่ได้รับสิทธิ์ เช่น เว็บเบราว์เซอร์ จะเรียกใช้เครื่องมือจัดการข้อมูลเข้าสู่ระบบในนามของบุคคลอื่นที่เชื่อถือโดยการตั้งค่าพารามิเตอร์ origin
ในเมธอดเครื่องมือจัดการข้อมูลเข้าสู่ระบบ GetCredentialRequest()
และ CreatePublicKeyCredentialRequest()
ผู้ให้บริการข้อมูลเข้าสู่ระบบจะดึงข้อมูล origin
โดยใช้ getOrigin()
API เพื่อประมวลผลคําขอเหล่านี้
หากต้องการเรียกข้อมูล origin
แอปผู้ให้บริการข้อมูลเข้าสู่ระบบต้องส่งรายการผู้เรียกที่มีสิทธิ์และเชื่อถือได้ไปยัง androidx.credentials.provider.CallingAppInfo's getOrigin()
API รายการที่อนุญาตนี้ต้องเป็นออบเจ็กต์ JSON ที่ถูกต้อง ระบบจะแสดงผล origin
หาก packageName
และลายนิ้วมือใบรับรองที่ได้รับจาก signingInfo
ตรงกับลายนิ้วมือของแอปที่พบใน privilegedAllowlist
ที่ส่งไปยัง getOrigin()
API หลังจากได้รับค่า origin
แล้ว แอปผู้ให้บริการควรถือว่านี่เป็นการโทรที่มีสิทธิ์ และตั้งค่า origin
นี้ในข้อมูลไคลเอ็นต์ใน AuthenticatorResponse
แทนการประมวลผล origin
โดยใช้ลายเซ็นของแอปการโทร
หากเรียกข้อมูล origin
ให้ใช้ clientDataHash
ที่ระบุไว้โดยตรงใน CreatePublicKeyCredentialRequest()
หรือ GetPublicKeyCredentialOption()
แทนการประกอบและแฮช clientDataJSON
ระหว่างคำขอลายเซ็น ตั้งค่าตัวยึดตำแหน่งสำหรับ clientDataJSON
ในเอกสารรับรองและการตอบกลับการยืนยันเพื่อหลีกเลี่ยงปัญหาการแยกวิเคราะห์ JSON
เครื่องมือจัดการรหัสผ่านบน Google ใช้รายการที่อนุญาตที่เผยแพร่ต่อสาธารณะสําหรับการเรียกใช้ getOrigin()
ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถใช้รายการนี้หรือระบุรายการของคุณเองในรูปแบบ JSON ที่ API อธิบายไว้ ผู้ให้บริการจะเลือกรายการที่จะใช้ หากต้องการเข้าถึงแบบสิทธิพิเศษจากผู้ให้บริการข้อมูลเข้าสู่ระบบบุคคลที่สาม โปรดดูเอกสารจากบุคคลที่สาม
เปิดใช้ผู้ให้บริการในอุปกรณ์
ผู้ใช้ต้องเปิดใช้ผู้ให้บริการผ่านการตั้งค่าอุปกรณ์ > รหัสผ่านและบัญชี > ผู้ให้บริการของคุณ > เปิดหรือปิดใช้
fun createSettingsPendingIntent(): PendingIntent