Trình quản lý thông tin xác thực là một tập hợp API ra mắt trong Android 14, hỗ trợ nhiều phương thức đăng nhập, chẳng hạn như tên người dùng – mật khẩu, khoá truy cập và các giải pháp đăng nhập liên kết (ví dụ như Đăng nhập bằng Google). Khi API Trình quản lý thông tin xác thực được gọi, hệ thống Android sẽ tổng hợp thông tin xác thực từ tất cả các trình cung cấp thông tin xác thực đã cài đặt trên thiết bị. Tài liệu này mô tả tập hợp các API cung cấp điểm cuối tích hợp cho các trình cung cấp thông tin xác thực này.
Thiết lập
Trước khi triển khai chức năng trong trình cung cấp thông tin xác thực, hãy hoàn tất các bước thiết lập có ở những phần sau.
Khai báo phần phụ thuộc
Trong tệp build.gradle
của mô-đun, hãy khai báo phần phụ thuộc bằng cách sử dụng phiên bản mới nhất của thư viện Trình quản lý thông tin xác thực:
implementation "androidx.credentials:credentials:1.2.0-{latest}"
Khai báo phần tử dịch vụ trong tệp kê khai
Trong tệp kê khai AndroidManifest.xml
của ứng dụng, hãy đưa vào một phần kê khai <service>
cho lớp dịch vụ mở rộng lớp CredentialProviderService
của thư viện androidx.credentials, như trong ví dụ dưới đây.
<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>
Quyền và bộ lọc ý định trình bày ở trên là phần không thể thiếu để quy trình của Trình quản lý thông tin xác thực hoạt động như mong đợi. Quyền đó là cần thiết để chỉ hệ thống Android mới có thể liên kết với dịch vụ này. Bộ lọc ý định đó dùng để phát hiện dịch vụ này với vai trò là trình cung cấp thông tin xác thực được Trình quản lý thông tin xác thực sử dụng.
Khai báo loại thông tin đăng nhập được hỗ trợ
Trong thư mục res/xml
, hãy tạo một tệp mới có tên là provider.xml
. Trong tệp này, hãy khai báo các loại thông tin đăng nhập mà dịch vụ của bạn hỗ trợ, thông qua các hằng số được xác định cho từng loại thông tin đăng nhập trong thư viện. Trong ví dụ sau, dịch vụ hỗ trợ mật khẩu truyền thống cũng như khoá truy cập, các hằng số được xác định là TYPE_PASSWORD_CREDENTIAL
và 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>
Ở những cấp độ API trước, trình cung cấp thông tin xác thực tích hợp với các API, chẳng hạn như API tự động điền đối với mật khẩu và dữ liệu khác. Các trình cung cấp này có thể sử dụng cùng một cơ sở hạ tầng nội bộ để lưu trữ những loại thông tin đăng nhập hiện có, đồng thời mở rộng cơ sở hạ tầng đó để hỗ trợ các loại thông tin đăng nhập khác, bao gồm cả khoá truy cập.
Cách tiếp cận 2 giai đoạn đối với hoạt động tương tác của trình cung cấp
Trình quản lý thông tin xác thực tương tác với trình cung cấp thông tin xác thực theo 2 giai đoạn:
- Giai đoạn đầu là giai đoạn bắt đầu/truy vấn, theo đó hệ thống sẽ liên kết với các dịch vụ của trình cung cấp thông tin xác thực và gọi phương thức
onBeginGetCredentialRequest()
,onBeginCreateCredentialRequest()
hoặconClearCredentialStateRequest()
bằng các yêu cầuBegin…
. Trình cung cấp phải xử lý các yêu cầu này và phản hồi bằng các phản hồiBegin…
. Đây là những phản hồi được điền sẵn bằng các mục nhập biểu thị cho các tuỳ chọn trực quan xuất hiện trên bộ chọn tài khoản. Bạn phải đặtPendingIntent
cho mỗi mục nhập. - Sau khi người dùng chọn một mục nhập, giai đoạn lựa chọn sẽ bắt đầu và
PendingIntent
liên kết với mục nhập đó được kích hoạt, do đó, hoạt động của trình cung cấp tương ứng sẽ hiện lên. Sau khi người dùng tương tác xong với hoạt động này, trình cung cấp thông tin xác thực phải đặt phản hồi cho kết quả của hoạt động trước khi kết thúc. Sau đó, phản hồi này được gửi tới ứng dụng khách đã gọi Trình quản lý thông tin xác thực.
Xử lý việc tạo khoá truy cập
Xử lý truy vấn tạo khoá truy cập
Khi một ứng dụng khách muốn tạo khoá truy cập và lưu trữ khoá đó bằng trình cung cấp thông tin xác thực, ứng dụng khách đó sẽ gọi API createCredential
. Để xử lý yêu cầu này trong dịch vụ của trình cung cấp thông tin xác thực sao cho khoá truy cập thực sự được lưu trữ trong bộ nhớ, hãy hoàn tất các bước ở những phần sau.
- Ghi đè phương thức
onBeginCreateCredentialRequest()
trong dịch vụ của bạn được mở rộng từCredentialProviderService
. - Xử lý
BeginCreateCredentialRequest
bằng cách tạoBeginCreateCredentialResponse
tương ứng và truyền mã này qua lệnh gọi lại. - Trong khi tạo
BeginCreateCredentialResponse
, hãy thêmCreateEntries
bắt buộc. MỗiCreateEntry
phải tương ứng với một tài khoản có thể lưu thông tin xác thực và phải đặtPendingIntent
cùng với siêu dữ liệu bắt buộc khác.
Ví dụ sau minh hoạ cách triển khai các bước này.
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
)
)
}
Trong quá trình tạo PendingIntent
, bạn cần tuân thủ những điều sau:
- Nên thiết lập Hoạt động tương ứng để hiện mọi lời nhắc bắt buộc về Sinh trắc học, thông tin xác nhận hoặc lựa chọn cần thiết.
- Nên đặt mọi dữ liệu bắt buộc (mà trình cung cấp cần đến khi hoạt động tương ứng được gọi) làm ý định bổ sung dùng để tạo
PendingIntent
, chẳng hạn nhưaccountId
trong quy trình tạo. - Phải tạo
PendingIntent
bằng cờPendingIntent.FLAG_MUTABLE
để hệ thống có thể thêm yêu cầu cuối cùng vào ý định bổ sung. - Không được tạo
PendingIntent
bằng cờPendingIntent.FLAG_ONE_SHOT
vì người dùng có thể chọn một mục nhập, quay lại và chọn lại mục nhập này. Điều này sẽ khiếnPendingIntent
kích hoạt 2 lần. - Phải tạo
PendingIntent
bằng một mã yêu cầu duy nhất để mỗi mục nhập đều có thể cóPendingIntent
tương ứng.
Xử lý lựa chọn mục nhập cho các yêu cầu tạo khoá truy cập
- Khi người dùng chọn một
CreateEntry
đã điền trước đây,PendingIntent
tương ứng sẽ được gọi vàActivity
của trình cung cấp liên kết sẽ được tạo. - Sau khi phương thức
onCreate
của Hoạt động được gọi, hãy truy cập ý định liên kết và truyền ý định đó vào lớpPendingIntentHander
để lấyProviderCreateCredentialRequest
. - Trích xuất
requestJson
,callingAppInfo
vàclientDataHash
từ yêu cầu. - Trích xuất
accountId
cục bộ từ ý định bổ sung. Đây là cách triển khai dành riêng cho ứng dụng mẫu và không bắt buộc. Mã tài khoản này có thể được dùng để lưu trữ thông tin xác thực này theo mã tài khoản cụ thể này. - Xác thực
requestJson
. Ví dụ bên dưới sử dụng các lớp dữ liệu cục bộ nhưPublicKeyCredentialCreationOptions
để chuyển đổi JSON đầu vào thành một lớp có cấu trúc theo thông số WebAuthn. Đối với trình cung cấp thông tin xác thực, bạn có thể thay thế thông số này bằng trình phân tích cú pháp riêng. - Kiểm tra asset-link của ứng dụng gọi nếu lệnh gọi bắt nguồn từ một ứng dụng Android gốc.
- Hiện lời nhắc xác thực. Ví dụ bên dưới sử dụng API sinh trắc học của Android.
- Khi xác thực thành công, hãy tạo một
credentialId
và một cặp khoá. - Lưu khoá riêng tư trong cơ sở dữ liệu cục bộ của bạn theo
callingAppInfo.packageName
. - Tạo một phản hồi JSON cho API xác thực web bao gồm khoá công khai và
credentialId
. Ví dụ bên dưới sử dụng các lớp tiện ích cục bộ nhưAuthenticatorAttestationResponse
vàFidoPublicKeyCredential
. Các lớp này giúp tạo JSON dựa trên thông số kỹ thuật đã đề cập trước đó. Là nhà cung cấp thông tin xác thực, bạn có thể thay thế các lớp này bằng trình tạo của riêng mình. - Tạo
CreatePublicKeyCredentialResponse
bằng JSON được tạo ở trên. - Đặt
CreatePublicKeyCredentialResponse
làm ý định bổ sung trênIntent
thông quaPendingIntentHander.setCreateCredentialResponse()
và đặt ý định đó thành kết quả của Hoạt động. - Hoàn tất Hoạt động.
Ví dụ về mã dưới đây minh hoạ các bước này. Mã này cần được xử lý trong lớp Hoạt động sau khi onCreate()
được gọi.
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)}"
}
Xử lý các truy vấn về yêu cầu tạo mật khẩu
Để xử lý các truy vấn về yêu cầu tạo mật khẩu, hãy làm như sau:
- Bên trong phương thức
processCreateCredentialRequest()
đề cập ở phần trước, hãy thêm một trường hợp khác vào khối chuyển đổi để xử lý các yêu cầu về mật khẩu. - Trong khi tạo
BeginCreateCredentialResponse
, hãy thêmCreateEntries
bắt buộc. - Mỗi
CreateEntry
cần phải tương ứng với một tài khoản có thể lưu thông tin xác thực và phải đặt mộtPendingIntent
trong đó cùng với các siêu dữ liệu khác.
Ví dụ sau đây minh hoạ cách triển khai các bước này:
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)
}
Xử lý lựa chọn mục nhập cho các yêu cầu tạo mật khẩu
Khi người dùng chọn một CreateEntry
được điền sẵn, PendingIntent
tương ứng sẽ thực thi và cho thấy Hoạt động được liên kết. Hãy truy cập ý định liên kết được truyền vào onCreate
rồi truyền ý định đó vào lớp PendingIntentHander
để lấy phương thức ProviderCreateCredentialRequest
.
Ví dụ dưới đây minh hoạ cách triển khai quy trình này. Mã này cần được xử lý trong phương thức onCreate()
của Hoạt động.
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()
Xử lý quy trình đăng nhập của người dùng
Quy trình đăng nhập của người dùng được xử lý theo các bước sau:
- Khi tìm cách đăng nhập cho người dùng, ứng dụng khách sẽ chuẩn bị một thực thể
GetCredentialRequest
. - Khung Android truyền yêu cầu này đến tất cả các trình cung cấp thông tin xác thực hiện có bằng cách liên kết với các dịch vụ này.
- Sau đó, dịch vụ của trình cung cấp sẽ nhận được
BeginGetCredentialRequest
chứa danh sáchBeginGetCredentialOption
. Trong đó mỗi danh sách chứa các tham số có thể dùng để truy xuất thông tin xác thực trùng khớp.
Để xử lý yêu cầu này trong dịch vụ của trình cung cấp thông tin xác thực, hãy hoàn tất các bước sau:
Ghi đè phương thức
onBeginGetCredentialRequest()
để xử lý yêu cầu. Lưu ý rằng nếu thông tin xác thực của bạn bị khoá, thì bạn có thể đặtAuthenticationAction
ngay trong phản hồi và thực hiện lệnh gọi lại.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()) } }
Các trình cung cấp (yêu cầu mở khoá thông tin xác thực trước khi trả về bất kỳ
credentialEntries
nào) phải thiết lập một ý định đang chờ xử lý để chuyển người dùng đến quy trình mở khoá của ứng dụng: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 ) ) }
Truy xuất thông tin xác thực từ cơ sở dữ liệu cục bộ và thiết lập bằng cách sử dụng
CredentialEntries
để được xuất hiện trong bộ chọn. Đối với khoá truy cập, bạn có thể đặtcredentialId
làm ý định bổ sung để biết ý định đó liên kết với thông tin xác thực nào khi người dùng chọn mục nhập này.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) }
Truy vấn thông tin xác thực từ cơ sở dữ liệu, tạo khoá truy cập và mục nhập mật khẩu cần điền.
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) ) }
Sau khi truy vấn và điền thông tin xác thực, giờ đây, bạn cần phải xử lý giai đoạn lựa chọn thông tin xác thực mà người dùng đã chọn, cho dù đó là khoá truy cập hay mật khẩu.
Xử lý lựa chọn của người dùng về khoá truy cập
- Trong phương thức
onCreate
của Hoạt động tương ứng, hãy truy xuất ý định liên kết rồi chuyển vàoPendingIntentHandler.retrieveProviderGetCredentialRequest()
. - Trích xuất
GetPublicKeyCredentialOption
từ yêu cầu đã truy xuất ở trên. Sau đó, hãy trích xuấtrequestJson
vàclientDataHash
từ tuỳ chọn này. - Trích xuất
credentialId
từ ý định bổ sung, do trình cung cấp thông tin xác thực điền vào khiPendingIntent
tương ứng được thiết lập. - Trích xuất khoá truy cập từ cơ sở dữ liệu cục bộ của bạn bằng cách sử dụng các tham số yêu cầu đã truy cập ở trên.
Hãy xác nhận rằng khoá truy cập phù hợp với siêu dữ liệu được trích xuất và quy trình xác minh người dùng.
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 )
Để xác thực người dùng, hãy hiện lời nhắc về Sinh trắc học (hoặc một phương thức xác nhận khác). Đoạn mã dưới đây sử dụng API sinh trắc học của Android.
Sau khi xác thực thành công, hãy tạo phản hồi JSON dựa trên thông số Xác thực web của W3. Trong đoạn mã dưới đây, các lớp dữ liệu của trình trợ giúp như
AuthenticatorAssertionResponse
được dùng để lấy các tham số có cấu trúc và chuyển đổi chúng thành định dạng JSON bắt buộc. Phản hồi này chứa chữ ký số trong khoá riêng tư của thông tin xác thực WebAuthn. Máy chủ của bên phụ thuộc có thể xác minh chữ ký này để xác thực người dùng trước khi đăng nhập.Tạo
PublicKeyCredential
bằng cách sử dụng JSON được tạo ở trên và đặt mã này vàoGetCredentialResponse
cuối cùng. Đặt phản hồi cuối cùng này vào kết quả của hoạt động này.
Ví dụ sau đây minh hoạ cách triển khai các bước này:
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)
Xử lý lựa chọn của người dùng về quy trình xác thực mật khẩu
- Trong hoạt động tương ứng, hãy truy cập ý định được truyền vào
onCreate
và trích xuấtProviderGetCredentialRequest
bằngPendingIntentHandler
. Sử dụng
GetPasswordOption
trong yêu cầu để truy xuất thông tin xác thực mật khẩu cho tên gói gửi đến.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 } }
Sau khi truy xuất, hãy đặt phản hồi cho thông tin xác thực mật khẩu đã chọn.
// Set the response back val result = Intent() val passwordCredential = PasswordCredential(username, password) PendingIntentHandler.setGetCredentialResponse( result, GetCredentialResponse(passwordCredential) ) setResult(Activity.RESULT_OK, result) finish()
Xử lý lựa chọn về mục nhập hành động xác thực
Như đã đề cập trước đó, trình cung cấp thông tin xác thực có thể đặt AuthenticationAction
nếu thông tin xác thực bị khoá. Nếu người dùng chọn mục nhập này, Hoạt động tương ứng với thao tác theo ý định được đặt trong PendingIntent
sẽ được gọi. Sau đó, trình cung cấp thông tin xác thực có thể hiển thị quy trình xác thực bằng sinh trắc học hoặc cơ chế tương tự để mở khoá thông tin xác thực. Khi thành công, trình cung cấp thông tin xác thực phải tạo BeginGetCredentialResponse
, tương tự như cách xử lý đăng nhập của người dùng được mô tả ở trên, vì thông tin xác thực hiện đã được mở khoá. Sau đó, phản hồi này phải được đặt thông qua phương thức PendingIntentHandler.setBeginGetCredentialResponse()
trước khi ý định theo dự tính được đặt làm kết quả và Hoạt động kết thúc.
Xoá yêu cầu về thông tin xác thực
Một ứng dụng khách có thể yêu cầu xoá mọi trạng thái được duy trì để chọn thông tin xác thực, chẳng hạn như trình cung cấp thông tin xác thực có thể ghi nhớ thông tin xác thực đã chọn trước đó và chỉ trả về thông tin xác thực đó vào lần tiếp theo. Một ứng dụng khách sẽ gọi API này và muốn xoá lựa chọn cố định. Dịch vụ của trình cung cấp thông tin xác thực của bạn có thể xử lý yêu cầu này bằng cách ghi đè phương thức onClearCredentialStateRequest()
:
override fun onClearCredentialStateRequest(
request: android.service.credentials.ClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>,
) {
// Delete any maintained state as appropriate.
}
Thêm chức năng liên kết đến trang cài đặt của nhà cung cấp
Để cho phép người dùng mở phần cài đặt của trình cung cấp từ màn hình Mật khẩu, khoá truy cập và tự động điền, ứng dụng trình cung cấp thông tin xác thực phải triển khai thuộc tính tệp kê khai credential-provider
settingsActivity
trong res/xml/provider.xml
. Thuộc tính này cho phép bạn dùng ý định để mở màn hình cài đặt riêng của ứng dụng nếu người dùng nhấp vào tên nhà cung cấp trong danh sách dịch vụ Mật khẩu, khoá truy cập và tự động điền. Đặt giá trị của thuộc tính này thành tên của hoạt động sẽ được chạy từ màn hình cài đặt.
<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>
Ý định cài đặt
Mở phần cài đặt: Ý định android.settings.CREDENTIAL_PROVIDER
sẽ hiện một màn hình cài đặt, trong đó người dùng có thể chọn trình cung cấp thông tin xác thực ưu tiên và trình cung cấp thông tin xác thực bổ sung.
Dịch vụ thông tin xác thực được ưu tiên: Ý định ACTION_REQUEST_SET_AUTOFILL_SERVICE
chuyển hướng người dùng đến màn hình lựa chọn nhà cung cấp được ưu tiên. Trình cung cấp được chọn trên màn hình này sẽ trở thành thông tin xác thực và trình cung cấp dịch vụ tự động điền ưu tiên.
Nhận danh sách cho phép đối với ứng dụng có đặc quyền
Các ứng dụng có đặc quyền như trình duyệt web thực hiện lệnh gọi qua Trình quản lý thông tin xác thực thay cho các bên đáng tin cậy khác bằng cách đặt tham số origin
trong Trình quản lý thông tin xác thực GetCredentialRequest()
và phương thức CreatePublicKeyCredentialRequest()
. Để xử lý những yêu cầu này, trình cung cấp thông tin xác thực sẽ truy xuất origin
bằng cách sử dụng API getOrigin()
.
Để truy xuất origin
, ứng dụng của nhà cung cấp thông tin xác thực cần truyền danh sách phương thức gọi có đặc quyền và đáng tin cậy đến API androidx.credentials.provider.CallingAppInfo's getOrigin()
. Danh sách cho phép này phải là một đối tượng JSON hợp lệ. origin
được trả về nếu packageName
và dấu vân tay chứng chỉ thu được từ signingInfo
khớp với dấu vân tay của một ứng dụng tìm thấy trong privilegedAllowlist
được truyền đến API getOrigin()
. Sau khi nhận được giá trị origin
, ứng dụng của nhà cung cấp sẽ coi đây là lệnh gọi đặc quyền và đặt origin
này trên dữ liệu ứng dụng trong AuthenticatorResponse
, thay vì tính toán origin
bằng chữ ký của ứng dụng gọi.
Nếu bạn truy xuất origin
, hãy sử dụng clientDataHash
được cung cấp trực tiếp trong CreatePublicKeyCredentialRequest()
hoặc GetPublicKeyCredentialOption()
thay vì tập hợp và băm clientDataJSON
trong quá trình yêu cầu chữ ký. Để tránh gặp vấn đề khi phân tích cú pháp JSON, hãy đặt giá trị phần giữ chỗ cho clientDataJSON
trong phản hồi chứng thực và xác nhận.
Trình quản lý mật khẩu của Google sử dụng một danh sách cho phép có sẵn công khai cho các lệnh gọi đến getOrigin()
. Là nhà cung cấp thông tin xác thực, có thể bạn phải sử dụng danh sách này hoặc cung cấp danh sách riêng ở định dạng JSON mà API mô tả. Nhà cung cấp có quyền chọn danh sách nào được sử dụng. Để có quyền truy cập đặc quyền với nhà cung cấp thông tin xác thực bên thứ ba, hãy tham khảo tài liệu do bên thứ ba cung cấp.
Bật trình cung cấp trên thiết bị
Người dùng phải bật trình cung cấp thông qua phần Cài đặt thiết bị > Mật khẩu và tài khoản > Trình cung cấp của bạn > Bật hoặc tắt.
fun createSettingsPendingIntent(): PendingIntent