Khả năng tương thích của phiên bản

Tính năng Khôi phục thông tin xác thực của Trình quản lý thông tin xác thực hoạt động trên các thiết bị chạy Android 9 (API cấp 28) trở lên, phiên bản chính của Dịch vụ Google Play (GMS) 242200000 (phiên bản 24.22) trở lên và phiên bản 1.5.0 trở lên của thư viện androidx.credentials. Trên các thiết bị không có Dịch vụ Google Play hoặc có phiên bản GMS cũ hơn, hãy bắt CreateCredentialProviderConfigurationException và GetCredentialProviderConfigurationException để ứng dụng của bạn quay lại quy trình đăng nhập chuẩn một cách linh hoạt.

Điều kiện tiên quyết

Định cấu hình máy chủ bên tin cậy WebAuthn tương tự như máy chủ cho khoá truy cập. Nếu đã thiết lập một máy chủ để xử lý việc xác thực bằng khoá truy cập, thì bạn có thể sử dụng lại cùng một điểm cuối đăng ký và xác nhận WebAuthn cho khoá khôi phục.

Phần phụ thuộc

Thêm các phần phụ thuộc sau vào tệp build.gradle của mô-đun ứng dụng:

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.7.0-alpha02")
    implementation("androidx.credentials:credentials-play-services-auth:1.7.0-alpha02")
}

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.7.0-alpha02"
    implementation "androidx.credentials:credentials-play-services-auth:1.7.0-alpha02"
}

Tính năng Khôi phục thông tin đăng nhập có trong phiên bản 1.5.0 trở lên của thư viện androidx.credentials. Tuy nhiên, bạn nên sử dụng các phiên bản ổn định mới nhất của phần phụ thuộc nếu có thể.

Ảnh hưởng của android:allowBackup đến tính năng Khôi phục thông tin đăng nhập

Thuộc tính tệp kê khai android:allowBackup kiểm soát việc sao lưu dữ liệu ứng dụng, tương tác với tính năng Khôi phục thông tin đăng nhập theo 3 cách riêng biệt:

  • Khôi phục quá trình chuyển khoá (độc lập với allowBackup): Dịch vụ Google Play chuyển cặp khoá RestoreCredential bằng cách sử dụng bản sao lưu trên đám mây được mã hoá hai đầu hoặc chuyển từ thiết bị này sang thiết bị khác (D2D) trong nội bộ, bất kể android:allowBackup là true hay false. Đối với quá trình chuyển D2D, khoá khôi phục luôn được chuyển; điều kiện duy nhất khiến khoá này không được chuyển là trong quá trình sao lưu lên đám mây khi người dùng đã chọn không sao lưu lên đám mây trong phần Cài đặt Android.
  • Khôi phục trong nền trong BackupAgent (yêu cầu android:allowBackup="true"): Tính năng đăng nhập thầm lặng trong nền trước khi người dùng mở ứng dụng trên thiết bị mới (BackupAgent.onRestoreFinished()) chỉ chạy khi android:allowBackup="true" và android:backupAgent được định cấu hình trong tệp kê khai. Nếu android:allowBackup="false", hãy khôi phục phiên khi người dùng khởi chạy Activity chính lần đầu tiên. Bạn cũng có thể triển khai cả hai phương pháp để giúp quá trình khôi phục trở nên hiệu quả hơn.
  • Kiểm thử tính năng Sao lưu lên đám mây của Android Studio (yêu cầu android:allowBackup="true"): Để kiểm thử loại sao lưu Lên đám mây trong công cụ Sao lưu và khôi phục của Android Studio, bạn cần có android:allowBackup="true", trong khi việc kiểm thử Từ thiết bị này sang thiết bị khác hoạt động bất kể allowBackup.

Tổng quan

Tính năng Khôi phục thông tin đăng nhập tuân theo vòng đời gồm 3 giai đoạn trên thiết bị cũ và thiết bị mới của người dùng:

  1. Tạo khoá khôi phục trên thiết bị trước đó: Sau khi người dùng đăng nhập (hoặc khi khởi động ứng dụng nếu người dùng đã đăng nhập và không có khoá khôi phục nào được ghi lại trong bộ nhớ cục bộ), hãy tìm nạp các tuỳ chọn tạo từ máy chủ của bên Relying Party và gọi credentialManager.createCredential().
  2. Đăng nhập bằng khoá khôi phục trên thiết bị mới: Truy xuất RestoreCredential đã chuyển bằng chiến lược hai cấp: trước tiên là truy xuất thầm lặng trong BackupAgent.onRestoreFinished() (khi android:allowBackup="true") để quá trình đồng bộ hoá trong nền và thông báo đẩy tiếp tục ngay lập tức, và thứ hai là truy xuất dự phòng trong trình chạy Activity.onCreate() trong lần khởi chạy ứng dụng đầu tiên.
  3. Xoá khoá khôi phục khi đăng xuất: Khi người dùng đăng xuất hoặc phiên của họ bị máy chủ vô hiệu hoá, hãy gọi credentialManager.clearCredentialState() bằng ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) và xoá cờ đồng bộ hoá cục bộ.

Tạo khoá khôi phục

Ứng dụng của bạn phải bao gồm tất cả các trường hợp người dùng đăng nhập để đảm bảo người dùng đang hoạt động đã tạo khoá khôi phục. Tạo khoá khôi phục trong các trường hợp sau:

  • Nếu người dùng đã đăng nhập và khoá khôi phục chưa được tạo (chẳng hạn như trong phương thức onCreate() cho Activity chính).
  • Khi người dùng đang đăng nhập hoặc hoàn tất quy trình đăng ký tài khoản mới.

Để tránh gọi createCredential() mỗi khi khởi chạy ứng dụng, hãy lưu trữ cờ đồng bộ hoá cục bộ, chẳng hạn như has_synced_restore_credential, trong bộ nhớ cục bộ. Đừng lưu trữ cờ này trong SharedPreferences hoặc DataStore được sao lưu mặc định, trừ phi tệp đó bị loại trừ trong data_extraction_rules.xml; nếu không, tính năng Tự động sao lưu sẽ sao chép has_synced_restore_credential = true vào thiết bị mới và ngăn thiết bị mới tạo hoặc xoay vòng khoá khôi phục của chính thiết bị đó.

Tạo thực thể Trình quản lý thông tin xác thực

Sử dụng bối cảnh hoạt động của ứng dụng để tạo thực thể cho đối tượng CredentialManager.

// Use your app or activity context to instantiate a client instance of
// CredentialManager.
private val credentialManager = CredentialManager.create(context)

Lấy các lựa chọn tạo thông tin xác thực từ máy chủ ứng dụng

Sử dụng thư viện tuân thủ FIDO trong máy chủ ứng dụng để gửi cho ứng dụng khách PublicKeyCredentialCreationOptions JSON cần thiết để tạo thông tin xác thực khôi phục. Để biết thêm thông tin về cách triển khai phía máy chủ, hãy xem phần Hướng dẫn phía máy chủ.

JSON của các lựa chọn tạo tuân theo cùng một cấu trúc như khi đăng ký khoá truy cập:

{
  "challenge": "<base64url-encoded challenge>",
  "rp": {
    "name": "<relying party name>",
    "id": "<relying party host name>"
  },
  "user": {
    "id": "<base64url-encoded user ID>",
    "name": "<user name>",
    "displayName": "<user display name>"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }
  ],
  "attestation": "none",
  "excludeCredentials": [
    {
        "id": "<base64url-encoded credential ID to exclude>", 
        "type": "public-key"
    }
  ],
  "authenticatorSelection": {
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

Đảm bảo rằng JSON của các lựa chọn tạo đáp ứng các yêu cầu sau:

  • challenge và user.id: Mã hoá cả hai giá trị dưới dạng chuỗi Base64URL không có khoảng đệm (thay thế + bằng -, / bằng _ và xoá = ở cuối). Các ký tự Base64 chuẩn hoặc mã nhận dạng số nguyên thô sẽ gây ra lỗi CreateRestoreCredentialDomException hoặc IllegalArgumentException.
  • rp.id: Đặt thành miền của bên bị phụ thuộc (ví dụ: example.com).
  • pubKeyCredParams: Bao gồm {"type": "public-key", "alg": -7} (ES256) và {"type": "public-key", "alg": -257} (RS256).

Tạo khoá khôi phục

Sau khi nhận được JSON về các lựa chọn tạo khoá công khai từ máy chủ, hãy gói JSON trong một đối tượng CreateRestoreCredentialRequest rồi gọi createCredential() trên đối tượng CredentialManager.

// requestJson contains the credential creation options sent by the server
val createRestoreRequest = CreateRestoreCredentialRequest(requestJson)
val response = try {
    credentialManager.createCredential(context, createRestoreRequest)
} catch (e: E2eeUnavailableException) {
    // Fall back to local device-to-device (D2D) restore if cloud E2EE is unavailable
    credentialManager.createCredential(
        context,
        CreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false),
    )
}

Các điểm chính về mã

  • Đối tượng CreateRestoreCredentialRequest chứa các trường sau:

    • requestJson: Các lựa chọn tạo thông tin xác thực do máy chủ ứng dụng gửi ở định dạng API xác thực web cho PublicKeyCredentialCreationOptionsJSON.
    • isCloudBackupEnabled: Trường Boolean để xác định xem có nên sao lưu khoá khôi phục lên đám mây hay không. Theo mặc định, cờ này là true. Trường này có các giá trị sau:

      • true: (Nên dùng) Giá trị này cho phép sao lưu khoá khôi phục lên đám mây nếu người dùng đã bật tính năng Sao lưu của Google và phương thức mã hoá hai đầu, chẳng hạn như phương thức khoá màn hình.
      • false: Giá trị này lưu khoá trên thiết bị chứ không lưu trên đám mây. Khoá này sẽ không có trên thiết bị mới nếu người dùng chọn khôi phục từ đám mây, nhưng vẫn chuyển trong quá trình thiết lập từ thiết bị này sang thiết bị khác (D2D) qua cáp cục bộ hoặc Wi-Fi.

Xử lý phản hồi tạo thông tin xác thực

API Trình quản lý thông tin xác thực trả về một phản hồi thuộc loại CreateRestoreCredentialResponse. Phản hồi này chứa phản hồi đăng ký thông tin xác thực khoá công khai ở định dạng JSON.

Gửi response.registrationResponseJson từ ứng dụng đến máy chủ của bên bị phụ thuộc để xác minh và lưu trữ khoá công khai cùng với tài khoản của người dùng, sau đó ghi lại điểm đánh dấu đồng bộ hoá cục bộ trong bộ nhớ cục bộ. Để biết thêm thông tin về quy trình xác minh phía máy chủ, hãy xem hướng dẫn về khoá truy cập.

Trong quá trình tạo khoá khôi phục, hãy xử lý các trường hợp ngoại lệ sau:

  • CreateRestoreCredentialDomException: Ngoại lệ này xảy ra nếu requestJson không hợp lệ và không tuân theo định dạng WebAuthn cho PublicKeyCredentialCreationOptionsJSON.
  • E2eeUnavailableException: Ngoại lệ này xảy ra nếu isCloudBackupEnabled là true, nhưng thiết bị của người dùng không có tính năng sao lưu dữ liệu hoặc mã hoá hai đầu, chẳng hạn như phương thức khoá màn hình.
    Để đảm bảo rằng Thông tin xác thực khôi phục được tạo cho quá trình chuyển dữ liệu cục bộ từ thiết bị này sang thiết bị khác (D2D) ngay cả khi không có E2EE trên đám mây, hãy bắt E2eeUnavailableException và thử lại ngay createCredential() bằng CreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false).
  • IllegalArgumentException: Ngoại lệ này xảy ra nếu requestJson trống, không phải là JSON hợp lệ hoặc không có user.id được mã hoá Base64URL hợp lệ tuân thủ thông số kỹ thuật của WebAuthn.

Đăng nhập bằng khoá khôi phục

Sử dụng tính năng Khôi phục thông tin đăng nhập để đăng nhập thầm lặng cho người dùng trong quá trình thiết lập thiết bị.

Lấy các lựa chọn truy xuất thông tin xác thực từ máy chủ ứng dụng

Trước khi gọi getCredential() trên thiết bị mới, hãy yêu cầu thử thách JSON WebAuthn PublicKeyCredentialRequestOptions từ máy chủ ứng dụng. Vì ứng dụng trên thiết bị mới chưa biết người dùng nào đang đăng nhập, nên điểm cuối này phải không được xác thực (khám phá không có người dùng). Để biết hướng dẫn liên quan đến máy chủ, hãy xem bài viết Đăng nhập bằng khoá truy cập và hướng dẫn xác thực phía máy chủ.

JSON của các tuỳ chọn truy xuất có cấu trúc như sau:

{
  "challenge": "<your app challenge>",
  "allowCredentials": [],
  "rpId": "<your app server domain>"
}

Định cấu hình JSON cho các tuỳ chọn truy xuất trên máy chủ của bạn như sau:

  • allowCredentials: Bỏ qua trường này hoặc truyền một mảng trống ([]) vì tài khoản của người dùng không được biết đến trước khi khôi phục. Sau khi getCredential() trả về, máy chủ của bạn sẽ xác định người dùng từ trường response.userHandle (khớp với user.id đã đăng ký trong quá trình tạo) và tra cứu khoá công khai theo id (credentialId).
  • userVerification: Đặt thành "discouraged" (hoặc "preferred"). Vì tính năng Khôi phục thông tin xác thực chạy ở chế độ nền mà không có giao diện người dùng sinh trắc học hoặc khoá màn hình, nên Android sẽ ghi đè "required" thành "discouraged" và dữ liệu trình xác thực được trả về máy chủ của bạn sẽ có cờ Đã xác minh người dùng (uv) được đặt thành false. Định cấu hình trình xác minh máy chủ WebAuthn để chấp nhận uv = false cho các câu nhận định về khoá khôi phục.

Lấy khoá khôi phục

Để lấy khoá khôi phục trên thiết bị mới, hãy gói requestJson của máy chủ trong một GetRestoreCredentialOption bên trong GetCredentialRequest và gọi getCredential() trên đối tượng CredentialManager.

Triển khai chiến lược khôi phục hai cấp để người dùng đăng nhập được dù quá trình khôi phục dữ liệu ứng dụng trong nền có chạy hay không:

  • Cấp 1 (Khôi phục trong nền trong BackupAgent.onRestoreFinished()): Nếu bạn bật tính năng sao lưu dữ liệu ứng dụng (android:allowBackup="true"), hãy phân lớp con BackupAgent và ghi đè onRestoreFinished() (không sử dụng onRestore(), vốn chỉ được gọi cho các bản sao lưu khoá-giá trị cũ). Vì onRestoreFinished() là đồng bộ và getCredential() là hàm suspend Kotlin, hãy kết nối lệnh gọi bằng runBlocking(Dispatchers.IO). Bắt NoCredentialException một cách thầm lặng khi không có khoá khôi phục.
  • Cấp 2 (Dự phòng trên nền trước trong trình chạy Activity.onCreate()): Trong lần khởi chạy ứng dụng đầu tiên khi không có phiên cục bộ nào, hãy gọi getCredential() từ lifecycleScope trước khi hiển thị màn hình đăng nhập. Thao tác này xử lý các ứng dụng có android:allowBackup="false" và khôi phục nếu thiết bị không có mạng khi onRestoreFinished() chạy.

Nếu bạn thêm một BackupAgent mới vào một ứng dụng đã sử dụng tính năng Tự động sao lưu với android:allowBackup="true", hãy đặt android:fullBackupOnly="true" trên phần tử <application> trong AndroidManifest.xml để Android tiếp tục sử dụng tính năng Tự động sao lưu thay vì chuyển sang tính năng sao lưu khoá-giá trị cũ.

    suspend fun getRestoreKey(
        fetchAuthenticationJson: () -> String,
    ) {
        // Fetch the options required to get the restore key
        val authenticationJson = fetchAuthenticationJson()

        // Create the GetRestoreCredentialOption object
        val options = GetRestoreCredentialOption(authenticationJson)
        val getRequest = GetCredentialRequest(listOf(options))

        try {
            val response = credentialManager.getCredential(context, getRequest)

            // Type-check and extract the restore credential
            val credential = response.credential
            if (credential is RestoreCredential) {
                val responseJson = credential.authenticationResponseJson
            }
        } catch (e: NoCredentialException) {
            // No restore key is present on this device
        }
    }

class MyBackupAgent : BackupAgent() {

    override fun onRestoreFinished() {
        super.onRestoreFinished()
        val credentialManager = CredentialManager.create(this)
        val restoreFunctions = RestoreCredentialsFunctions(this, credentialManager)

        runBlocking(Dispatchers.IO) {
            restoreFunctions.getRestoreKey(
                fetchAuthenticationJson = {
                    /* Fetch challenge JSON from server */ ""
                }
            )
        }
    }
}

Credential Manager API trả về một GetCredentialResponse có thuộc tính credential là RestoreCredential (không phải PublicKeyCredential) chứa authenticationResponseJson.

Xử lý phản hồi đăng nhập

Gửi restoreCredential.authenticationResponseJson từ ứng dụng đến máy chủ bên tin cậy để xác minh câu nhận định mật mã và cấp mã phiên mới cho người dùng. Để biết thông tin chi tiết về quy trình xác minh phía máy chủ, hãy xem bài viết Đăng nhập bằng khoá truy cập.

Khi thiết kế bộ nhớ phụ trợ và chính sách vòng đời cho khoá khôi phục, hãy làm theo các nguyên tắc sau:

  • Phân biệt khoá khôi phục với khoá truy cập của người dùng: Gắn thẻ khoá khôi phục trong cơ sở dữ liệu để ẩn khoá đó khỏi màn hình quản lý khoá truy cập dành cho người dùng và miễn kiểm tra xác minh người dùng (uv = true).
  • Hỗ trợ nhiều thiết bị đang hoạt động cho mỗi người dùng: Người dùng có thể có nhiều thiết bị (chẳng hạn như điện thoại và máy tính bảng). Cho phép nhiều khoá khôi phục đang hoạt động cho mỗi tài khoản người dùng (ví dụ: khoá theo thiết bị hoặc mã cài đặt) thay vì ghi đè một hàng.
  • Đặt TTL thích hợp và dọn dẹp các khoá mồ côi: Việc gỡ cài đặt ứng dụng hoặc xoá dữ liệu ứng dụng sẽ xoá khoá khôi phục cục bộ mà không thông báo cho máy chủ của bạn. Giữ lại khoá khôi phục đủ lâu để duy trì trong khoảng thời gian nâng cấp thiết bị (ngay cả khi người dùng đăng xuất trên thiết bị cũ sau khi chuyển) và loại bỏ các khoá cũ dựa trên TTL không hoạt động hoặc giới hạn khoá cho mỗi người dùng.

Xoá khoá khôi phục

Trình quản lý thông tin xác thực không có trạng thái và không nhận biết được hoạt động của người dùng, vì vậy, trình quản lý này không tự động xoá khoá khôi phục sau khi sử dụng. Để xoá khoá khôi phục, hãy gọi phương thức clearCredentialState(). Để đảm bảo bảo mật, hãy xoá khoá bất cứ khi nào người dùng đăng xuất để họ không bị đăng nhập lại một cách thầm lặng trong lần khởi chạy ứng dụng tiếp theo.

Khoá khôi phục chỉ bị xoá trong các trường hợp sau:

  • Hành động ở cấp hệ thống: Người dùng gỡ cài đặt ứng dụng hoặc xoá dữ liệu của ứng dụng.
  • Lệnh gọi cấp ứng dụng: Xoá khoá theo phương thức lập trình bằng cách gọi clearCredentialState() khi xử lý việc đăng xuất của người dùng trong mã của ứng dụng.

Vì ClearCredentialStateRequest() mặc định chỉ xoá trạng thái phiên chuẩn và giữ nguyên khoá khôi phục, nên hãy thực hiện xoá kép khi đăng xuất:

  1. Gọi credentialManager.clearCredentialState(ClearCredentialStateRequest()) để xoá trạng thái phiên của trình cung cấp thông tin xác thực đang hoạt động.
  2. Gọi credentialManager.clearCredentialState() bằng ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) để xoá RestoreCredential đã lưu trữ.
  3. Xoá điểm đánh dấu has_synced_restore_credential cục bộ khỏi bộ nhớ cục bộ.
// Clear active credential provider session state
credentialManager.clearCredentialState(ClearCredentialStateRequest())

// When the user logs out, delete the restore key
val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)
val response = credentialManager.clearCredentialState(clearRequest)

Các bước tiếp theo