Credential Manager หมายถึงชุด API ที่เปิดตัวใน Android 14 ซึ่งรองรับวิธีการลงชื่อเข้าใช้หลายวิธี เช่น ผู้ใช้และรหัสผ่าน พาสคีย์ และโซลูชันการลงชื่อเข้าใช้แบบรวมศูนย์ (เช่น ฟีเจอร์ลงชื่อเข้าใช้ด้วย Google) เมื่อ 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 ที่แสดงด้านบนเป็นส่วนสำคัญสำหรับเอกสารรับรอง ขั้นตอนของผู้จัดการเพื่อให้ทำงานได้ตามที่คาดไว้ สิทธิ์นี้จําเป็นเพื่อให้มีเพียงระบบ 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
ของกิจกรรมแล้ว ให้ไปที่ ที่เกี่ยวข้อง และส่งไปยังคลาสPendingIntentHander
เพื่อให้ProviderCreateCredentialRequest
- ดึงข้อมูล
requestJson
,callingAppInfo
และclientDataHash
จาก อีกครั้ง - ดึง
accountId
ในพื้นที่ออกจากข้อมูลเพิ่มเติมของ Intent นี่เป็นตัวอย่างการใช้งานเฉพาะแอปและไม่จำเป็นต้องทำ ใช้รหัสบัญชีนี้ได้ เพื่อจัดเก็บข้อมูลเข้าสู่ระบบนี้กับรหัสบัญชีที่เฉพาะเจาะจงนี้ - ตรวจสอบ
requestJson
ตัวอย่างด้านล่างใช้คลาสข้อมูลในเครื่อง เช่นPublicKeyCredentialCreationOptions
เพื่อแปลง JSON อินพุตเป็นคลาสที่มีโครงสร้างตามข้อกำหนดของ WebAuthn ในฐานะผู้ให้บริการข้อมูลเข้าสู่ระบบ คุณสามารถแทนที่คลาสนี้ด้วยโปรแกรมแยกวิเคราะห์ของคุณเองได้ - ตรวจสอบ asset-link สําหรับแอปที่โทร หากการโทรมาจากแอป Android ดั้งเดิม
- แสดงข้อความแจ้งให้ตรวจสอบสิทธิ์ ตัวอย่างด้านล่างใช้ Android ข้อมูลไบโอเมตริก 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
เรียกใช้และแสดงกิจกรรมที่เกี่ยวข้อง เข้าถึง
ที่เกี่ยวข้องที่ส่งใน 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
ในการตอบกลับและเรียกใช้ Callbackprivate 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
เป็นส่วนเพิ่มเติมของความตั้งใจเพื่อให้รู้ว่าข้อมูลเข้าสู่ระบบใด จะจับคู่เมื่อผู้ใช้เลือกรายการนี้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 เพิ่มเติมซึ่งมีการเติมข้อมูลโดย เมื่อมีการตั้งค่า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.
}
เพิ่มความสามารถในการลิงก์ไปยังหน้าการตั้งค่าของผู้ให้บริการ
หากต้องการอนุญาตให้ผู้ใช้เปิดการตั้งค่าของผู้ให้บริการจากรหัสผ่าน
พาสคีย์ และ ป้อนข้อความอัตโนมัติ แอปของผู้ให้บริการข้อมูลเข้าสู่ระบบควรใช้
credential-provider
แอตทริบิวต์ไฟล์ Manifest 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>
ความตั้งใจในการตั้งค่า
เปิดการตั้งค่า: android.settings.CREDENTIAL_PROVIDER
Intent จะแสดงหน้าจอการตั้งค่าซึ่งผู้ใช้สามารถเลือก
ผู้ให้บริการข้อมูลเข้าสู่ระบบเพิ่มเติม
บริการข้อมูลเข้าสู่ระบบที่ต้องการ: Intent ACTION_REQUEST_SET_AUTOFILL_SERVICE
จะเปลี่ยนเส้นทางผู้ใช้ไปยังหน้าจอการเลือกผู้ให้บริการที่ต้องการ ผู้ให้บริการที่เลือกในหน้าจอนี้
จะเป็นผู้ให้บริการป้อนข้อความอัตโนมัติและข้อมูลรับรองที่ต้องการ
รับรายการที่อนุญาตของแอปที่มีสิทธิ์
แอปที่ได้รับสิทธิ์ เช่น เว็บเบราว์เซอร์ เรียกใช้เครื่องมือจัดการข้อมูลเข้าสู่ระบบในนามของ
พาร์ทเนอร์รายอื่นที่พึ่งพาโดยการตั้งค่าพารามิเตอร์ origin
ในข้อมูลเข้าสู่ระบบ
ผู้จัดการ GetCredentialRequest()
และ
CreatePublicKeyCredentialRequest()
วิธี ผู้ให้บริการข้อมูลเข้าสู่ระบบจะดึงข้อมูล origin
โดยใช้ getOrigin()
API เพื่อประมวลผลคําขอเหล่านี้
หากต้องการเรียกข้อมูล origin
แอปของผู้ให้บริการข้อมูลเข้าสู่ระบบจะต้องส่งอยู่ในรายการ
ผู้โทรที่ได้รับสิทธิ์และไว้วางใจได้ไปยัง
API androidx.credentials.provider.CallingAppInfo's getOrigin()
รายการที่อนุญาตนี้
ต้องเป็นออบเจ็กต์ 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