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 ระยะ ดังนี้
- ระยะที่ 1 คือระยะเริ่มต้น/การค้นหา ซึ่งระบบจะเชื่อมโยงกับบริการของผู้ให้บริการข้อมูลเข้าสู่ระบบและเรียกใช้เมธอด
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 ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถแทนที่คลาสนี้ด้วยโปรแกรมแยกวิเคราะห์ของคุณเองได้ - ตรวจสอบ asset-link สําหรับแอปโทรหากการโทรมาจากแอป 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()
ที่กล่าวถึงในส่วนก่อนหน้า ให้เพิ่มเคสอื่นในบล็อก Switch เพื่อจัดการคำขอรหัสผ่าน - ขณะสร้าง
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
ในการตอบกลับและเรียกใช้การเรียกกลับได้ทันที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
ระหว่างคำขอลายเซ็น หากต้องการหลีกเลี่ยงปัญหาการแยกวิเคราะห์ JSON ให้ตั้งค่าค่าตัวยึดตําแหน่งสําหรับ clientDataJSON
ในการตอบกลับการรับรองและการยืนยัน
เครื่องมือจัดการรหัสผ่านบน Google ใช้รายการที่อนุญาตที่เผยแพร่ต่อสาธารณะสําหรับการเรียกใช้ getOrigin()
ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถใช้รายการนี้หรือระบุรายการของคุณเองในรูปแบบ JSON ที่ API อธิบายไว้ ผู้ให้บริการจะเลือกรายการที่จะใช้ หากต้องการรับสิทธิ์เข้าถึงที่มีสิทธิ์ด้วยผู้ให้บริการข้อมูลเข้าสู่ระบบบุคคลที่สาม โปรดดูเอกสารประกอบที่บุคคลที่สามให้ไว้
เปิดใช้ผู้ให้บริการในอุปกรณ์
ผู้ใช้ต้องเปิดใช้ผู้ให้บริการผ่านการตั้งค่าอุปกรณ์ > รหัสผ่านและบัญชี > ผู้ให้บริการของคุณ > เปิดหรือปิดใช้
fun createSettingsPendingIntent(): PendingIntent