Đăng nhập cho người dùng bằng Trình quản lý thông tin xác thực

Trình quản lý thông tin xác thực là một API Jetpack hỗ trợ nhiều phương thức đăng nhập, chẳng hạn như đăng nhập bằng tên người dùng và mật khẩu, khoá truy cập và các giải pháp đăng nhập được liên kết (chẳng hạn như Đăng nhập bằng Google) trong một API duy nhất, từ đó đơn giản hoá quá trình tích hợp cho nhà phát triển.

Hơn nữa, đối với người dùng, Trình quản lý thông tin xác thực sẽ hợp nhất giao diện đăng nhập trên các phương thức xác thực, giúp người dùng đăng nhập vào ứng dụng một cách rõ ràng và dễ dàng hơn, bất kể họ chọn phương thức nào.

Trang này giải thích khái niệm khoá truy cập và các bước triển khai việc hỗ trợ ở phía máy khách cho các giải pháp xác thực (bao gồm cả khoá truy cập) bằng cách sử dụng Credential Manager API. Ngoài ra, bạn cũng có thể truy cập vào một trang Câu hỏi thường gặp riêng để tìm câu trả lời cho những câu hỏi cụ thể và chi tiết hơn.

Ý kiến phản hồi của bạn có vai trò quan trọng trong việc cải thiện Credential Manager API. Hãy chia sẻ mọi vấn đề bạn phát hiện được hoặc các ý tưởng giúp cải thiện API bằng đường liên kết sau:

Gửi ý kiến phản hồi

Giới thiệu về khoá truy cập

Khoá truy cập là phương thức thay thế an toàn và dễ dàng hơn cho mật khẩu. Với khoá truy cập, người dùng có thể đăng nhập vào các ứng dụng và trang web nhờ cảm biến sinh trắc học (chẳng hạn như dùng vân tay hoặc công nghệ nhận dạng khuôn mặt), mã PIN hoặc hình mở khoá. Khoá truy cập này mang lại trải nghiệm đăng nhập liền mạch, giúp người dùng không phải nhớ tên người dùng hoặc mật khẩu.

Khoá truy cập dựa vào WebAuthn (Ứng dụng xác thực người dùng truy cập Internet), một tiêu chuẩn do Liên minh FIDO và tổ chức the World Wide Web Consortium (W3C) cùng phát triển. WebAuthn sử dụng tiêu chuẩn mã hoá khoá công khai để xác thực người dùng. Trang web hoặc ứng dụng mà người dùng đăng nhập vào có thể xem và lưu trữ khoá công khai, nhưng tuyệt đối không nhận được khoá riêng tư. Khoá riêng tư này được giữ bí mật và an toàn. Chính vì khoá này là duy nhất cũng như gắn liền với trang web hoặc ứng dụng, nên khoá truy cập sẽ không thể đánh cắp được, nhờ đó tăng cường bảo mật.

Người dùng có thể sử dụng Trình quản lý thông tin xác thực để tạo khoá truy cập và lưu trữ các khoá đó trong Trình quản lý mật khẩu của Google.

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

Để sử dụng Trình quản lý thông tin xác thực, hãy hoàn thành các bước trong phần này.

Sử dụng phiên bản nền tảng gần đây

Hỗ trợ Trình quản lý thông tin xác thực trên Android 4.4 (API cấp 19) trở lên.

Thêm phần phụ thuộc vào ứng dụng

Thêm các phần phụ thuộc sau đây vào tập lệnh bản dựng của mô-đun ứng dụng:

Groovy

dependencies {
    implementation "androidx.credentials:credentials:1.0.0-alpha02

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation "androidx.credentials:credentials-play-services-auth:1.0.0-alpha02"
}

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.0.0-alpha02")

    // optional - needed for credentials support from play services, for devices running
    // Android 13 and below.
    implementation("androidx.credentials:credentials-play-services-auth:1.0.0-alpha02")
}

Giữ nguyên các lớp trong tệp ProGuard

Trong tệp proguard-rules.pro của mô-đun, hãy thêm các lệnh sau:

-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
  *;
}

Tìm hiểu thêm về cách rút gọn, làm rối mã nguồn và tối ưu hoá ứng dụng.

Thêm tính năng hỗ trợ cho Digital Asset Links (Đường liên kết đến tài sản kỹ thuật số)

Để bật tính năng hỗ trợ khoá truy cập cho ứng dụng Android, hãy liên kết ứng dụng của bạn với một trang web mà ứng dụng đó sở hữu. Bạn có thể khai báo mối liên kết này bằng cách hoàn thành các bước sau:

  1. Tạo tệp JSON chứa Digital Asset Links (Đường liên kết đến tài sản kỹ thuật số). Ví dụ: để khai báo rằng trang web https://signin.example.com và ứng dụng Android có tên gói com.example có thể dùng chung thông tin đăng nhập, hãy tạo một tệp có tên assetlinks.json với nội dung sau:

    [
      {
        "relation" : [
          "delegate_permission/common.handle_all_urls",
          "delegate_permission/common.get_login_creds"
        ],
        "target" : {
          "namespace" : "android_app",
          "package_name" : "com.example.android",
          "sha256_cert_fingerprints" : [
            SHA_HEX_VALUE
          ]
        }
      }
    ]
    

    Trường relation là một mảng gồm một hoặc nhiều chuỗi mô tả mối quan hệ đang được khai báo. Để khai báo rằng các ứng dụng và trang web dùng chung thông tin đăng nhập, hãy chỉ định các mối quan hệ là delegate_permission/handle_all_urlsdelegate_permission/common.get_login_creds.

    Trường target là đối tượng chỉ định tài sản mà nội dung khai báo sẽ áp dụng. Sau đây là các trường xác định một trang web:

    namespace web
    site

    URL của trang web, có định dạng https://domain[:optional_port]; ví dụ: https://www.example.com.

    domain phải đủ điều kiện và phải bỏ qua optional_port khi sử dụng cổng 443 cho HTTPS.

    Đích site chỉ được là miền gốc: bạn không thể giới hạn liên kết ứng dụng với một thư mục con cụ thể. Đừng thêm một đường dẫn vào URL, chẳng hạn như dấu gạch chéo ở cuối.

    Miền con không được cân nhắc để khớp: nghĩa là nếu bạn chỉ định domainwww.example.com, thì miền www.counter.example.com sẽ không được liên kết với ứng dụng của bạn.

    Sau đây là các trường xác định một ứng dụng Android:

    namespace android_app
    package_name Tên gói được khai báo trong tệp kê khai của ứng dụng. Ví dụ: com.example.android
    sha256_cert_fingerprints Các vân tay số SHA256 trong chứng chỉ ký của ứng dụng.
  2. Lưu trữ tệp JSON chứa Digital Asset Links (Đường liên kết đến tài sản kỹ thuật số) tại vị trí sau trên miền đăng nhập:

    https://domain[:optional_port]/.well-known/assetlinks.json
    

    Ví dụ: nếu miền đăng nhập của bạn là signin.example.com, hãy lưu trữ tệp JSON tại https://signin.example.com/.well-known/assetlinks.json.

    Loại MIME cho tệp Digital Asset Link (Đường liên kết đến tài sản kỹ thuật số) phải là JSON. Hãy đảm bảo máy chủ gửi một tiêu đề Content-Type: application/json trong phản hồi.

  3. Đảm bảo rằng máy chủ lưu trữ cho phép Google truy xuất tệp Digital Asset Links (Đường liên kết đến tài sản kỹ thuật số) của bạn. Nếu bạn có tệp robots.txt, tệp đó phải cho phép tác nhân Googlebot truy xuất /.well-known/assetlinks.json. Hầu hết các trang web có thể cho phép mọi tác nhân tự động hoá truy xuất tệp trong đường dẫn /.well-known/ để các dịch vụ khác có thể truy cập vào siêu dữ liệu trong những tệp đó:

    User-agent: *
    Allow: /.well-known/
    
  4. Thêm dòng sau vào tệp kê khai trong <application>:

    <meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
    
  5. Nếu bạn đang dùng tính năng đăng nhập bằng mật khẩu thông qua Trình quản lý thông tin xác thực, hãy làm theo bước này để định cấu hình việc liên kết đến tài sản kỹ thuật số trong tệp kê khai. Nếu chỉ sử dụng khoá truy cập, bạn không cần phải thực hiện bước này.

    Khai báo mối liên kết trong ứng dụng Android. Thêm một đối tượng chỉ định các tệp assetlinks.json cần tải. Bạn phải thoát khỏi mọi dấu nháy đơn và dấu ngoặc kép mà bạn dùng trong chuỗi. Ví dụ:

    <string name="asset_statements" translatable="false">
    [{
      \"include\": \"https://signin.example.com/.well-known/assetlinks.json\"
    }]
    </string>
    
    > GET /.well-known/assetlinks.json HTTP/1.1
    > User-Agent: curl/7.35.0
    > Host: signin.example.com
    
    < HTTP/1.1 200 OK
    < Content-Type: application/json
    

Định cấu hình Trình quản lý thông tin xác thực

Để định cấu hình và khởi tạo đối tượng CredentialManager, hãy thêm logic tương tự như sau:

Kotlin

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

Java

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

Chỉ báo trường thông tin đăng nhập

Trên Android 14 trở lên, bạn có thể dùng thuộc tính isCredential để chỉ báo trường thông tin đăng nhập, chẳng hạn như trường tên người dùng hoặc mật khẩu. Thuộc tính này cho biết rằng thành phần hiển thị này là một trường thông tin đăng nhập sẽ hoạt động với Trình quản lý thông tin xác thực và nhà cung cấp thông tin đăng nhập bên thứ ba, đồng thời giúp các dịch vụ tự động điền cung cấp nội dung gợi ý tự động điền hiệu quả hơn. Khi ứng dụng sử dụng API Trình quản lý thông tin xác thực, bảng dưới cùng của Trình quản lý thông tin xác thực có thông tin đăng nhập sẽ xuất hiện, và tính năng tự động điền không cần hiện hộp thoại điền tên người dùng hoặc mật khẩu nữa.

Để sử dụng thuộc tính isCredential, hãy thêm thuộc tính này vào các Thành phần hiển thị liên quan:

<TextView
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:isCredential="true"
...
 />

Đăng nhập người dùng

Để truy xuất mọi tuỳ chọn mật khẩu và khoá truy cập được liên kết với tài khoản của người dùng, hãy hoàn tất các bước sau:

  1. Khởi tạo các tuỳ chọn xác thực bằng mật khẩu và khoá truy cập:

    Kotlin

    // Retrieves the user's saved password for your app from their
    // password provider.
    val getPasswordOption = GetPasswordOption()
    
    // Get passkey from the user's public key credential provider.
    val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(
        requestJson = requestJson
    )

    Java

    // Retrieves the user's saved password for your app from their
    // password provider.
    GetPasswordOption getPasswordOption = new GetPasswordOption();
    
    // Get passkey from the user's public key credential provider.
    GetPublicKeyCredentialOption getPublicKeyCredentialOption =
            new GetPublicKeyCredentialOption(requestJson);
  2. Sử dụng các lựa chọn được truy xuất từ bước trước đây để tạo yêu cầu đăng nhập.

    Kotlin

    val getCredRequest = GetCredentialRequest(
        listOf(getPasswordOption, getPublicKeyCredentialOption)
    )

    Java

    GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder()
        .addCredentialOption(getPasswordOption)
        .addCredentialOption(getPublicKeyCredentialOption)
        .build();
  3. Khởi động quy trình đăng nhập:

    Kotlin

    coroutineScope.launch {
        try {
            val result = credentialManager.getCredential(
                // Use an activity-based context to avoid undefined system UI
                // launching behavior.
                context = activityContext,
                request = getCredRequest
            )
            handleSignIn(result)
        } catch (e : GetCredentialException) {
            handleFailure(e)
        }
    }
    
    fun handleSignIn(result: GetCredentialResponse) {
        // Handle the successfully returned credential.
        val credential = result.credential
    
        when (credential) {
            is PublicKeyCredential -> {
                val responseJson = credential.authenticationResponseJson
                // Share responseJson i.e. a GetCredentialResponse on your server to
                // validate and  authenticate
            }
            is PasswordCredential -> {
                val username = credential.id
                val password = credential.password
                // Use id and password to send to your server to validate
                // and authenticate
            }
          is CustomCredential -> {
              // If you are also using any external sign-in libraries, parse them
              // here with the utility functions provided.
              if (credential.type == ExampleCustomCredential.TYPE)  {
              try {
                  val ExampleCustomCredential = ExampleCustomCredential.createFrom(credential.data)
                  // Extract the required credentials and complete the authentication as per
                  // the federated sign in or any external sign in library flow
                  } catch (e: ExampleCustomCredential.ExampleCustomCredentialParsingException) {
                      // Unlikely to happen. If it does, you likely need to update the dependency
                      // version of your external sign-in library.
                      Log.e(TAG, "Failed to parse an ExampleCustomCredential", e)
                  }
              } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential")
              }
            } else -> {
                // Catch any unrecognized credential type here.
                Log.e(TAG, "Unexpected type of credential")
            }
        }
    }

    Java

    credentialManager.getCredentialAsync(
        // Use activity based context to avoid undefined
        // system UI launching behavior
        activity,
        getCredRequest,
        cancellationSignal,
        <executor>,
        new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
            @Override
            public void onResult(GetCredentialResponse result) {
                handleSignIn(result);
            }
    
            @Override
            public void onError(GetCredentialException e) {
                handleFailure(e);
            }
        }
    );
    
    public void handleSignIn(GetCredentialResponse result) {
        // Handle the successfully returned credential.
        Credential credential = result.getCredential();
        if (credential instanceof PublicKeyCredential) {
            String responseJson = ((PublicKeyCredential) credential).getAuthenticationResponseJson();
            // Share responseJson i.e. a GetCredentialResponse on your server to validate and authenticate
        } else if (credential instanceof PasswordCredential) {
            String username = ((PasswordCredential) credential).getId();
            String password = ((PasswordCredential) credential).getPassword();
            // Use id and password to send to your server to validate and authenticate
        } else if (credential instanceof CustomCredential) {
            if (ExampleCustomCredential.TYPE.equals(credential.getType())) {
                try {
                    ExampleCustomCredential customCred = ExampleCustomCredential.createFrom(customCredential.getData());
                    // Extract the required credentials and complete the
                    // authentication as per the federated sign in or any external
                    // sign in library flow
                } catch (ExampleCustomCredential.ExampleCustomCredentialParsingException e) {
                    // Unlikely to happen. If it does, you likely need to update the
                    // dependency version of your external sign-in library.
                    Log.e(TAG, "Failed to parse an ExampleCustomCredential", e);
                }
            } else {
                // Catch any unrecognized custom credential type here.
                Log.e(TAG, "Unexpected type of credential");
            }
        } else {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential");
        }
    }

Ví dụ sau đây cho thấy cách định dạng yêu cầu JSON khi bạn nhận được một khoá truy cập:

{
  "challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo",
  "allowCredentials": [],
  "timeout": 1800000,
  "userVerification": "required",
  "rpId": "credential-manager-app-test.glitch.me"
}

Ví dụ sau đây cho thấy phản hồi JSON trông như thế nào sau khi bạn nhận được thông tin xác thực khoá công khai:

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
    "signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
    "userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
  }
}

Xử lý các trường hợp ngoại lệ khi không có thông tin xác thực

Trong một số trường hợp, có thể người dùng sẽ không có thông tin xác thực hoặc không đồng ý sử dụng một thông tin xác thực hiện có. Nếu getCredential() được gọi và không tìm thấy thông tin xác thực, thì hệ thống sẽ trả về NoCredentialException. Nếu trường hợp này xảy ra, mã của bạn sẽ xử lý các thực thể NoCredentialException.

Kotlin

try {
  val credential = credentialManager.getCredential(credentialRequest)
} catch (e: NoCredentialException) {
  Log.e("CredentialManager", "No credential available", e)
}

Java

try {
  Credential credential = credentialManager.getCredential(credentialRequest);
} catch (NoCredentialException e) {
  Log.e("CredentialManager", "No credential available", e);
}

Trên Android 14 trở lên, bạn có thể giảm độ trễ khi hiện bộ chọn tài khoản thông qua phương thức prepareGetCredential() trước khi gọi getCredential().

Kotlin

val response = credentialManager.prepareGetCredential(
  GetCredentialRequest(
    listOf(
      <getPublicKeyCredentialOption>,
      <getPasswordOption>
    )
  )
}

Java

GetCredentialResponse response = credentialManager.prepareGetCredential(
  new GetCredentialRequest(
    Arrays.asList(
      new PublicKeyCredentialOption(),
      new PasswordOption()
    )
  )
);

Phương thức prepareGetCredential() không gọi các phần tử trên giao diện người dùng mà chỉ giúp bạn thực hiện công việc chuẩn bị để sau này có thể chạy thao tác get-credential còn lại (có liên quan đến giao diện người dùng) thông qua API getCredential().

Dữ liệu đã lưu vào bộ nhớ đệm sẽ được trả về trong đối tượng PrepareGetCredentialResponse. Nếu có sẵn thông tin xác thực, kết quả sẽ được lưu vào bộ nhớ đệm để sau này, bạn có thể chạy API getCredential() còn lại nhằm hiển thị bộ chọn tài khoản cùng với dữ liệu đã lưu vào bộ nhớ đệm.

Quy trình đăng ký

Bạn có thể đăng ký phương thức xác thực cho người dùng bằng khoá truy cập hoặc mật khẩu.

Tạo một khoá truy cập

Để giúp người dùng có lựa chọn đăng ký khoá truy cập và dùng khoá truy cập đó cho quá trình xác thực lại, hãy đăng ký thông tin xác thực người dùng bằng cách sử dụng đối tượng CreatePublicKeyCredentialRequest.

Kotlin

fun createPasskey(requestJson: String, preferImmediatelyAvailableCredentials: Boolean) {
    val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(
        // Contains the request in JSON format. Uses the standard WebAuthn
        // web JSON spec.
        requestJson = requestJson,
        // Defines whether you prefer to use only immediately available
        // credentials, not hybrid credentials, to fulfill this request.
        // This value is false by default.
        preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
    )

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    coroutineScope.launch {
        try {
            val result = credentialManager.createCredential(
                // Use an activity-based context to avoid undefined system
                // UI launching behavior
                context = activityContext,
                request = createPublicKeyCredentialRequest,
            )
            handlePasskeyRegistrationResult(result)
        } catch (e : CreateCredentialException){
            handleFailure(e)
        }
    }
}

fun handleFailure(e: CreateCredentialException) {
    when (e) {
        is CreatePublicKeyCredentialDomException -> {
            // Handle the passkey DOM errors thrown according to the
            // WebAuthn spec.
            handlePasskeyError(e.domError)
        }
        is CreateCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to register the credential.
        }
        is CreateCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is CreateCredentialProviderConfigurationException -> {
            // Your app is missing the provider configuration dependency.
            // Most likely, you're missing the
            // "credentials-play-services-auth" module.
        }
        is CreateCredentialUnknownException -> ...
        is CreateCredentialCustomException -> {
            // You have encountered an error from a 3rd-party SDK. If you
            // make the API call with a request object that's a subclass of
            // CreateCustomCredentialRequest using a 3rd-party SDK, then you
            // should check for any custom exception type constants within
            // that SDK to match with e.type. Otherwise, drop or log the
            // exception.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java.name}")
    }
}

Java

public void createPasskey(String requestJson, boolean preferImmediatelyAvailableCredentials) {
    CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest =
            // `requestJson` contains the request in JSON format. Uses the standard
            // WebAuthn web JSON spec.
            // `preferImmediatelyAvailableCredentials` defines whether you prefer
            // to only use immediately available credentials, not  hybrid credentials,
            // to fulfill this request. This value is false by default.
            new CreatePublicKeyCredentialRequest(
                requestJson, preferImmediatelyAvailableCredentials);

    // Execute CreateCredentialRequest asynchronously to register credentials
    // for a user account. Handle success and failure cases with the result and
    // exceptions, respectively.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined system
        // UI launching behavior
        requireActivity(),
        createPublicKeyCredentialRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleSuccessfulCreatePasskeyResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                if (e instanceof CreatePublicKeyCredentialDomException) {
                    // Handle the passkey DOM errors thrown according to the
                    // WebAuthn spec.
                    handlePasskeyError(((CreatePublicKeyCredentialDomException)e).getDomError());
                } else if (e instanceof CreateCredentialCancellationException) {
                    // The user intentionally canceled the operation and chose not
                    // to register the credential.
                } else if (e instanceof CreateCredentialInterruptedException) {
                    // Retry-able error. Consider retrying the call.
                } else if (e instanceof CreateCredentialProviderConfigurationException) {
                    // Your app is missing the provider configuration dependency.
                    // Most likely, you're missing the
                    // "credentials-play-services-auth" module.
                } else if (e instanceof CreateCredentialUnknownException) {
                } else if (e instanceof CreateCredentialCustomException) {
                    // You have encountered an error from a 3rd-party SDK. If
                    // you make the API call with a request object that's a
                    // subclass of
                    // CreateCustomCredentialRequest using a 3rd-party SDK,
                    // then you should check for any custom exception type
                    // constants within that SDK to match with e.type.
                    // Otherwise, drop or log the exception.
                } else {
                  Log.w(TAG, "Unexpected exception type "
                          + e.getClass().getName());
                }
            }
        }
    );
}

Định dạng yêu cầu JSON

Sau khi tạo một khoá truy cập, bạn phải liên kết khoá truy cập đó với một tài khoản của người dùng và lưu trữ khoá công khai của khoá truy cập trên máy chủ của bạn. Ví dụ về mã sau đây cho thấy cách định dạng yêu cầu JSON khi bạn tạo một khoá truy cập.

Bài đăng này trên blog về xác thực liền mạch trong ứng dụng sẽ hướng dẫn bạn cách định dạng yêu cầu JSON khi tạo mã xác thực và khi xác thực bằng khoá truy cập. Bài đăng này cũng giải thích lý do mật khẩu không phải là giải pháp xác thực hiệu quả, cùng với cách tận dụng thông tin xác thực sinh trắc học có sẵn, cách liên kết ứng dụng với trang web mà bạn sở hữu, cách tạo mã xác thực và cách xác thực bằng cách sử dụng mã xác thực.

{
  "challenge": "abc123",
  "rp": {
    "name": "Credential Manager example",
    "id": "credential-manager-test.example.com"
  },
  "user": {
    "id": "def456",
    "name": "helloandroid@gmail.com",
    "displayName": "helloandroid@gmail.com"
  },
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    },
    {
      "type": "public-key",
      "alg": -257
    }
  ],
  "timeout": 1800000,
  "attestation": "none",
  "excludeCredentials": [
    {"id": "ghi789", "type": "public-key"},
    {"id": "jkl012", "type": "public-key"}
  ],
  "authenticatorSelection": {
    "authenticatorAttachment": "platform",
    "requireResidentKey": true,
    "residentKey": "required",
    "userVerification": "required"
  }
}

Đặt giá trị cho authenticatorAttachment

Bạn chỉ có thể đặt tham số authenticatorAttachment tại thời điểm tạo thông tin xác thực. Bạn có thể chỉ định platform, cross-platform hoặc không giá trị nào. Trong hầu hết mọi trường hợp, bạn không nên sử dụng giá trị nào.

  • platform: Để đăng ký thiết bị hiện tại của người dùng hoặc nhắc người dùng sử dụng mật khẩu nâng cấp lên khoá truy cập sau khi đăng nhập, hãy đặt authenticatorAttachment thành platform.
  • cross-platform: Giá trị này thường dùng khi đăng ký thông tin xác thực đa yếu tố và không được dùng trong ngữ cảnh khoá truy cập.
  • Không có giá trị: Để cung cấp cho người dùng sự linh hoạt trong việc tạo khoá truy cập trên thiết bị ưu tiên của họ (chẳng hạn như trong phần cài đặt tài khoản), bạn không nên chỉ định tham số authenticatorAttachment khi người dùng chọn thêm khoá truy cập. Trong hầu hết mọi trường hợp, tốt nhất là bạn không nên chỉ định tham số.

Ngăn việc tạo khoá truy cập trùng lặp

Liệt kê mã thông tin xác thực trong mảng excludeCredentials (không bắt buộc) để ngăn việc tạo khoá truy cập mới nếu đã có một khoá truy cập của cùng một nhà cung cấp khoá truy cập.

Xử lý phản hồi JSON

Đoạn mã sau đây minh hoạ phản hồi JSON mẫu để tạo thông tin xác thực khoá công khai. Tìm hiểu thêm về cách xử lý thông tin đăng nhập của khoá công khai trả về.

{
  "id": "KEDetxZcUfinhVi6Za5nZQ",
  "type": "public-key",
  "rawId": "KEDetxZcUfinhVi6Za5nZQ",
  "response": {
    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
    "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A"
  }
}

Xác minh nguồn gốc dữ liệu JSON của ứng dụng

origin đại diện cho ứng dụng hoặc trang web đưa ra yêu cầu và được khoá truy cập dùng để chống lại các cuộc tấn công giả mạo. Máy chủ ứng dụng của bạn cần phải kiểm tra nguồn gốc dữ liệu ứng dụng theo một danh sách cho phép gồm các ứng dụng và trang web đã được phê duyệt. Nếu máy chủ nhận được yêu cầu của một ứng dụng hoặc trang web từ một nguồn không xác định, thì yêu cầu đó sẽ bị từ chối.

Trong trường hợp Web, origin cho biết có cùng nguồn gốc với trang web nơi thông tin đăng nhập được nhập. Ví dụ: với URL là https://www.example.com:8443/store?category=shoes#athletic, origin sẽ là https://www.example.com:8443.

Đối với các ứng dụng Android, tác nhân người dùng sẽ tự động đặt origin thành chữ ký của ứng dụng gọi. Chữ ký này phải được xác minh là trùng khớp trên máy chủ của bạn để xác thực phương thức gọi của API khoá truy cập. origin Android là một URI bắt nguồn từ hàm băm SHA-256 của chứng chỉ ký APK, chẳng hạn như:

android:apk-key-hash:<sha256_hash-of-apk-signing-cert>

Bạn có thể tìm thấy hàm băm SHA-256 của chứng chỉ ký này trong một kho khoá bằng cách chạy lệnh sau trong cửa sổ dòng lệnh:

keytool -list -keystore <path-to-apk-signing-keystore>

Hàm băm SHA-256 ở định dạng thập lục phân được phân tách bằng dấu hai chấm (91:F7:CB:F9:D6:81…) và các giá trị origin của Android được mã hoá base64url. Ví dụ dùng đoạn mã Python này minh hoạ cách chuyển đổi định dạng hàm băm sang một định dạng thập lục phân tương thích được phân tách bằng dấu hai chấm:

import binascii
import base64
fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5'
print("android:apk-key-hash:" + base64.urlsafe_b64encode(binascii.a2b_hex(fingerprint.replace(':', ''))).decode('utf8').replace('=', ''))

Hãy thay thế giá trị của fingerprint bằng giá trị của riêng bạn. Dưới đây là một kết quả mẫu:

android:apk-key-hash:kffL-daBUxvHpY-4M8yhTavt5QnFEI2LsexohxrGPYU

Sau đó, bạn có thể so khớp chuỗi đó dưới dạng một nguồn gốc được phép trên máy chủ của bạn. Nếu bạn có nhiều chứng chỉ ký (chẳng hạn như chứng chỉ để gỡ lỗi và phát hành) hoặc nhiều ứng dụng, hãy lặp lại quy trình và chấp nhận mọi nguồn gốc đó đều hợp lệ trên máy chủ.

Lưu mật khẩu của người dùng

Nếu người dùng cung cấp tên người dùng và mật khẩu cho luồng xác thực trong ứng dụng, thì bạn có thể đăng ký thông tin xác thực người dùng để dùng cho mục đích xác thực người dùng. Để thực hiện việc này, hãy tạo đối tượng CreatePasswordRequest:

Kotlin

fun registerPassword(username: String, password: String) {
    // Initialize a CreatePasswordRequest object.
    val createPasswordRequest =
            CreatePasswordRequest(id = username, password = password)

    // Create credential and handle result.
    coroutineScope.launch {
        try {
            val result =
                credentialManager.createCredential(
                    // Use an activity based context to avoid undefined
                    // system UI launching behavior.
                    activityContext,
                    createPasswordRequest
                  )
            handleRegisterPasswordResult(result)
        } catch (e: CreateCredentialException) {
            handleFailure(e)
        }
    }
}

Java

void registerPassword(String username, String password) {
    // Initialize a CreatePasswordRequest object.
    CreatePasswordRequest createPasswordRequest =
        new CreatePasswordRequest(username, password);

    // Register the username and password.
    credentialManager.createCredentialAsync(
        // Use an activity-based context to avoid undefined
        // system UI launching behavior
        requireActivity(),
        createPasswordRequest,
        cancellationSignal,
        executor,
        new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
            @Override
            public void onResult(CreateCredentialResponse result) {
                handleResult(result);
            }

            @Override
            public void onError(CreateCredentialException e) {
                handleFailure(e);
            }
        }
    );
}

Hỗ trợ khôi phục thông tin đăng nhập

Nếu không còn quyền truy cập vào thiết bị lưu trữ thông tin đăng nhập, thì người dùng có thể khôi phục thông qua bản sao lưu bảo mật trực tuyến. Để tìm hiểu thêm về cách hỗ trợ quá trình khôi phục thông tin xác thực này, hãy đọc phần có tiêu đề "Khôi phục quyền truy cập hoặc thêm thiết bị mới" trong bài đăng này trên blog: Tính bảo mật của khoá truy cập trong Trình quản lý mật khẩu của Google.

Thêm tính năng hỗ trợ các công cụ quản lý mật khẩu bằng URL phổ biến của điểm cuối khoá truy cập

Để tích hợp liền mạch và đảm bảo khả năng tương thích sau này với các công cụ quản lý mật khẩu và thông tin xác thực, bạn nên thêm tính năng hỗ trợ các URL phổ biến của điểm cuối khoá truy cập. Đây là một giao thức mở cho các bên liên kết để chính thức quảng cáo tính năng hỗ trợ khoá truy cập cũng như cung cấp các đường liên kết trực tiếp để đăng ký và quản lý khoá truy cập.

  1. Đối với một bên phụ thuộc tại https://example.com, có một trang web cùng với các ứng dụng Android và iOS, URL phổ biến sẽ là https://example.com/.well-known/passkey-endpoints.
  2. Khi URL được truy vấn, phản hồi phải sử dụng lược đồ sau

    {
      "enroll": "https://example.com/account/manage/passkeys/create"
      "manage": "https://example.com/account/manage/passkeys"
    }
    
  3. Để mở đường liên kết này ngay trong ứng dụng của bạn thay vì trên web, hãy sử dụng đường liên kết trong ứng dụng Android.

  4. Bạn có thể xem thêm thông tin chi tiết trong tài liệu giải thích về URL phổ biến của điểm cuối khoá truy cập trên GitHub.

Khắc phục các lỗi thường gặp

Bảng sau đây trình bày mã và nội dung mô tả của một vài lỗi thường gặp, đồng thời cung cấp một số thông tin về nguyên nhân gây ra các lỗi đó:

Mã lỗi và nội dung mô tả Nguyên nhân
Lỗi khi bắt đầu đăng nhập: 16: Phương thức gọi tạm thời bị chặn do có quá nhiều lời nhắc đăng nhập bị huỷ.

Nếu trong quá trình phát triển, bạn gặp phải lỗi thời gian chờ 24 giờ, bạn có thể đặt lại khoảng thời gian đó bằng cách xoá bộ nhớ ứng dụng của Dịch vụ Google Play.

Ngoài ra, để bật/tắt thời gian chờ này trên trình mô phỏng hoặc thiết bị kiểm thử, hãy chuyển đến ứng dụng Trình quay số rồi nhập mã sau: *#*#66382723#*#*. Ứng dụng Trình quay số sẽ xoá tất cả thông tin đầu vào và có thể đóng, nhưng sẽ không có thông báo xác nhận.

Lỗi khi bắt đầu đăng nhập: 8: Lỗi nội bộ không xác định.
  1. Thiết bị chưa được thiết lập đúng cách bằng Tài khoản Google.
  2. Định dạng JSON của khoá truy cập được tạo không đúng.
CreatePublicKeyCredentialDomException: Không xác thực được yêu cầu đến Mã gói của ứng dụng chưa được đăng ký với máy chủ. Hãy xác thực mã này trong quá trình tích hợp phía máy chủ.
CreateCredentialUnknownException: Trong quá trình lưu mật khẩu, đã phát hiện phản hồi lỗi mật khẩu từ Cơ chế một lần chạm 16: Bỏ qua quá trình lưu mật khẩu vì có khả năng người dùng được tính năng Tự động điền của Android nhắc Lỗi này chỉ xảy ra trên Android 13 trở xuống, và chỉ khi Google là nhà cung cấp dịch vụ Tự động điền. Trong trường hợp này, người dùng sẽ thấy lời nhắc thực hiện thao tác lưu từ tính năng Tự động điền, đồng thời mật khẩu sẽ được lưu vào Trình quản lý mật khẩu của Google. Xin lưu ý rằng thông tin đăng nhập được lưu bởi tính năng Tự động điền bằng Google sẽ được chia sẻ hai chiều với Credential Manager API. Do đó, bạn có thể bỏ qua lỗi này một cách an toàn.

Tài nguyên khác

Để tìm hiểu thêm về Credential Manager API và khoá truy cập, hãy xem các tài nguyên sau: