Menerapkan Pulihkan Kredensial dengan Credential Manager

Halaman ini menjelaskan cara membuat, login dengan, dan menghapus kunci pemulihan.

Kompatibilitas versi

Restore Credentials dari Credential Manager berfungsi di perangkat yang menjalankan Android 9 (API level 28) dan yang lebih tinggi, layanan Google Play (GMS) core versi 242200000 (v24.22) atau yang lebih tinggi, dan library androidx.credentials versi 1.5.0 atau yang lebih tinggi. Di perangkat tanpa layanan Google Play atau dengan versi GMS yang lebih lama, tangkap CreateCredentialProviderConfigurationException dan GetCredentialProviderConfigurationException agar aplikasi Anda beralih dengan lancar ke login standar.

Prasyarat

Konfigurasikan server pihak tepercaya WebAuthn yang mirip dengan server untuk kunci sandi. Jika Anda sudah menyiapkan server untuk menangani autentikasi dengan kunci sandi, Anda dapat menggunakan kembali endpoint pendaftaran dan pernyataan WebAuthn yang sama untuk kunci pemulihan.

Dependensi

Tambahkan dependensi berikut ke file build.gradle modul aplikasi Anda:

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"
}

Restore Credentials tersedia dari library androidx.credentials versi 1.5.0 dan yang lebih tinggi. Namun, sebaiknya gunakan versi stabil terbaru dari dependensi jika memungkinkan.

Pengaruh android:allowBackup terhadap Restore Credentials

Atribut manifes android:allowBackup mengontrol pencadangan data aplikasi, yang berinteraksi dengan Restore Credentials dalam tiga cara berbeda:

  • Transfer kunci pemulihan (independen allowBackup): Layanan Google Play mentransfer pasangan kunci RestoreCredential menggunakan pencadangan cloud terenkripsi end-to-end atau transfer antarperangkat (D2D) lokal, terlepas dari apakah android:allowBackup adalah true atau false. Untuk transfer D2D, kunci pemulihan selalu ditransfer; satu-satunya kondisi yang tidak memungkinkan transfer adalah selama pencadangan cloud saat pengguna memilih untuk tidak menggunakan pencadangan cloud di Setelan Android.
  • Pemulihan latar belakang di BackupAgent (memerlukan android:allowBackup="true"): Login latar belakang otomatis sebelum pengguna membuka aplikasi di perangkat baru (BackupAgent.onRestoreFinished()) hanya berjalan saat android:allowBackup="true" dan android:backupAgent dikonfigurasi dalam manifes Anda. Jika android:allowBackup="false", pulihkan sesi saat pengguna pertama kali meluncurkan Activity utama Anda. Anda juga dapat menerapkan kedua pendekatan tersebut untuk membuat pemulihan lebih andal.
  • Pengujian Pencadangan Cloud Android Studio (memerlukan android:allowBackup="true"): Menguji jenis pencadangan Cloud di alat Pencadangan dan Pemulihan Android Studio memerlukan android:allowBackup="true", sedangkan pengujian Perangkat ke Perangkat berfungsi terlepas dari allowBackup.

Ringkasan

Restore Credentials mengikuti siklus proses tiga tahap di seluruh perangkat lama dan baru pengguna:

  1. Membuat kunci pemulihan di perangkat sebelumnya: Setelah pengguna login (atau saat aplikasi dimulai jika pengguna sudah login dan tidak ada kunci pemulihan yang dicatat di penyimpanan lokal), ambil opsi pembuatan dari server pihak tepercaya Anda dan panggil credentialManager.createCredential().
  2. Login dengan kunci pemulihan di perangkat baru: Ambil RestoreCredential yang ditransfer menggunakan strategi dua tingkat: pertama, secara otomatis tanpa gangguan di BackupAgent.onRestoreFinished() (saat android:allowBackup="true") sehingga sinkronisasi latar belakang dan notifikasi push segera dilanjutkan, dan kedua, sebagai penggantian di peluncur Activity.onCreate() saat aplikasi pertama kali diluncurkan.
  3. Hapus kunci pemulihan saat logout: Saat pengguna logout atau sesinya dibatalkan oleh server, panggil credentialManager.clearCredentialState() dengan ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) dan hapus flag sinkronisasi lokal Anda.

Membuat kunci pemulihan

Aplikasi Anda harus mencakup semua kasus login pengguna untuk memastikan pengguna aktif memiliki kunci pemulihan yang dibuat. Buat kunci pemulihan dalam skenario berikut:

  • Jika pengguna login dan kunci pemulihan belum dibuat (seperti dalam metode onCreate() untuk Activity utama).
  • Saat pengguna login atau menyelesaikan alur pendaftaran akun baru.

Untuk menghindari pemanggilan createCredential() pada setiap peluncuran aplikasi, simpan flag sinkronisasi lokal seperti has_synced_restore_credential di dalam penyimpanan lokal. Jangan simpan flag ini di SharedPreferences atau DataStore yang dicadangkan secara default, kecuali jika file tersebut dikecualikan di data_extraction_rules.xml; jika tidak, Pencadangan Otomatis akan menyalin has_synced_restore_credential = true ke perangkat baru dan mencegah perangkat baru membuat atau merotasi kunci pemulihannya sendiri.

Membuat instance Credential Manager

Gunakan konteks aktivitas aplikasi Anda untuk membuat instance objek CredentialManager.

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

Mendapatkan opsi pembuatan kredensial dari server aplikasi Anda

Gunakan library yang mematuhi FIDO di server aplikasi Anda untuk mengirim JSON PublicKeyCredentialCreationOptions yang diperlukan untuk membuat kredensial pemulihan ke aplikasi klien Anda. Untuk mengetahui informasi selengkapnya tentang penerapan sisi server, lihat Panduan sisi server.

JSON opsi pembuatan mengikuti struktur yang sama dengan pendaftaran kunci sandi:

{
  "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"
  }
}

Pastikan JSON opsi pembuatan Anda memenuhi persyaratan berikut:

  • challenge dan user.id: Enkode kedua nilai sebagai string Base64URL tanpa padding (mengganti + dengan -, / dengan _, dan menghapus = di akhir). Karakter Base64 standar atau ID bilangan bulat mentah menyebabkan CreateRestoreCredentialDomException atau IllegalArgumentException.
  • rp.id: Tetapkan ke domain pihak tepercaya Anda (misalnya, example.com).
  • pubKeyCredParams: Sertakan {"type": "public-key", "alg": -7} (ES256) dan {"type": "public-key", "alg": -257} (RS256).

Membuat kunci pemulihan

Setelah menerima JSON opsi pembuatan kunci publik dari server Anda, gabungkan JSON dalam objek CreateRestoreCredentialRequest dan panggil createCredential() pada objek CredentialManager.

// createRestoreRequest contains the details sent by the server 
val response = credentialManager.createCredential(context, createRestoreRequest)

Poin penting tentang kode

  • Objek CreateRestoreCredentialRequest berisi kolom-kolom berikut:

    • requestJson: Opsi pembuatan kredensial yang dikirim oleh server aplikasi dalam format Web Authentication API untuk PublicKeyCredentialCreationOptionsJSON.
    • isCloudBackupEnabled: Kolom Boolean untuk menentukan apakah kunci pemulihan harus dicadangkan ke cloud. Secara default, flag ini adalah true. Kolom ini memiliki nilai berikut:

      • true: (Direkomendasikan) Nilai ini memungkinkan pencadangan kunci pemulihan ke cloud jika pengguna telah mengaktifkan Pencadangan Google dan enkripsi end-to-end, seperti kunci layar.
      • false: Nilai ini menyimpan kunci secara lokal dan bukan di cloud. Kunci tidak tersedia di perangkat baru jika pengguna memilih untuk memulihkan dari cloud, tetapi masih ditransfer selama penyiapan device-to-device (D2D) kabel lokal atau Wi-Fi.

Menangani respons pembuatan kredensial

Credential Manager API menampilkan respons jenis CreateRestoreCredentialResponse. Respons ini menyimpan respons pendaftaran kredensial kunci publik dalam format JSON.

Kirim response.registrationResponseJson dari aplikasi Anda ke server pihak tepercaya untuk memverifikasi dan menyimpan kunci publik bersama akun pengguna, lalu catat penanda sinkronisasi lokal Anda di penyimpanan lokal. Untuk mengetahui informasi selengkapnya tentang verifikasi sisi server, lihat panduan untuk kunci sandi.

Selama proses pembuatan kunci pemulihan, tangani pengecualian berikut:

  • CreateRestoreCredentialDomException: Pengecualian ini terjadi jika requestJson tidak valid dan tidak mengikuti format WebAuthn untuk PublicKeyCredentialCreationOptionsJSON.
  • E2eeUnavailableException: Pengecualian ini terjadi jika isCloudBackupEnabled adalah true, tetapi perangkat pengguna tidak memiliki pencadangan data atau enkripsi end-to-end, seperti kunci layar.
    Untuk memastikan Kredensial Pemulihan dibuat untuk transfer perangkat ke perangkat (D2D) lokal meskipun E2EE cloud tidak tersedia, tangkap E2eeUnavailableException dan segera coba lagi createCredential() dengan CreateRestoreCredentialRequest(requestJson, isCloudBackupEnabled = false).
  • IllegalArgumentException: Pengecualian ini terjadi jika requestJson kosong, bukan JSON yang valid, atau tidak memiliki user.id berenkode Base64URL yang valid dan sesuai dengan spesifikasi WebAuthn.

Login dengan kunci pemulihan

Gunakan Restore Credentials untuk otomatis login pengguna tanpa gangguan selama proses penyiapan perangkat.

Mendapatkan opsi pengambilan kredensial dari server aplikasi

Sebelum memanggil getCredential() di perangkat baru, minta verifikasi login JSON PublicKeyCredentialRequestOptions WebAuthn dari server aplikasi Anda. Karena aplikasi di perangkat baru belum mengetahui pengguna mana yang login, endpoint ini harus tidak diautentikasi (penemuan tanpa pengguna). Untuk panduan server terkait, lihat Login dengan kunci sandi dan panduan autentikasi sisi server.

JSON opsi pengambilan memiliki struktur berikut:

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

Konfigurasikan JSON opsi pengambilan di server Anda sebagai berikut:

  • allowCredentials: Hapus kolom ini atau teruskan array kosong ([]) karena akun pengguna tidak diketahui sebelum pemulihan. Setelah getCredential() menampilkan hasil, server Anda akan mengidentifikasi pengguna dari kolom response.userHandle (yang cocok dengan user.id yang didaftarkan selama pembuatan) dan mencari kunci publik berdasarkan id (credentialId).
  • userVerification: Tetapkan ke "discouraged" (atau "preferred"). Karena Kredensial Pemulihan berjalan secara otomatis tanpa UI biometrik atau kunci layar, Android mengganti "required" menjadi "discouraged", dan data pengautentikasi yang ditampilkan ke server Anda memiliki flag Pengguna Terverifikasi (uv) yang ditetapkan ke false. Konfigurasikan pemverifikasi server WebAuthn Anda untuk menerima uv = false untuk pernyataan kunci pemulihan.

Mendapatkan kunci pemulihan

Untuk mendapatkan kunci pemulihan di perangkat baru, gabungkan requestJson server dalam GetRestoreCredentialOption di dalam GetCredentialRequest dan panggil getCredential() pada objek CredentialManager.

Terapkan strategi pemulihan dua tingkat agar pengguna login, baik pemulihan data aplikasi latar belakang berjalan atau tidak:

  • Tingkat 1 (Pemulihan latar belakang di BackupAgent.onRestoreFinished()): Jika pencadangan data aplikasi diaktifkan (android:allowBackup="true"), buat subclass BackupAgent dan ganti onRestoreFinished() (jangan gunakan onRestore(), yang hanya dipanggil untuk pencadangan nilai kunci lama). Karena onRestoreFinished() bersifat sinkron dan getCredential() adalah fungsi suspend Kotlin, hubungkan panggilan menggunakan runBlocking(Dispatchers.IO). Menangkap NoCredentialException secara diam-diam saat tidak ada kunci pemulihan.
  • Tingkat 2 (Penggantian latar depan di peluncur Activity.onCreate()): Saat aplikasi pertama kali diluncurkan dan tidak ada sesi lokal, panggil getCredential() dari lifecycleScope sebelum menampilkan layar login. Tindakan ini menangani aplikasi dengan android:allowBackup="false" dan memulihkan jika perangkat offline saat onRestoreFinished() berjalan.

Jika Anda menambahkan BackupAgent baru ke aplikasi yang sudah menggunakan Pencadangan Otomatis dengan android:allowBackup="true", tetapkan juga android:fullBackupOnly="true" pada elemen <application> di AndroidManifest.xml agar Android terus menggunakan Pencadangan Otomatis, bukan beralih ke pencadangan nilai kunci lama.

// Fetch the options required to get the restore key
val authenticationJson = fetchAuthenticationJson()

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

val response = credentialManager.getCredential(context, getRequest)

// Type-check and extract the restore credential
val credential = response.credential as RestoreCredential

Credential Manager API menampilkan GetCredentialResponse yang properti credential-nya adalah RestoreCredential (bukan PublicKeyCredential) yang berisi authenticationResponseJson.

Menangani respons login

Kirim restoreCredential.authenticationResponseJson dari aplikasi ke server pihak tepercaya Anda untuk memverifikasi pernyataan kriptografis dan menerbitkan token sesi baru untuk pengguna. Untuk mengetahui detail verifikasi sisi server, lihat Login dengan kunci sandi.

Saat mendesain penyimpanan backend dan kebijakan siklus proses untuk kunci pemulihan, ikuti panduan berikut:

  • Bedakan kunci pemulihan dari kunci sandi pengguna: Beri tag pada kunci pemulihan di database Anda agar disembunyikan dari layar pengelolaan kunci sandi yang ditampilkan kepada pengguna dan dikecualikan dari pemeriksaan verifikasi pengguna (uv = true).
  • Mendukung beberapa perangkat aktif per pengguna: Seorang pengguna dapat memiliki beberapa perangkat (seperti ponsel dan tablet). Izinkan beberapa kunci pemulihan aktif per akun pengguna (misalnya, diberi kunci berdasarkan ID perangkat atau penginstalan), bukan menimpa satu baris.
  • Tetapkan TTL yang sesuai dan bersihkan kunci tanpa induk: Meng-uninstal aplikasi atau menghapus data aplikasi akan menghapus kunci pemulihan lokal tanpa memberi tahu server Anda. Pertahankan kunci pemulihan cukup lama agar dapat bertahan selama periode upgrade perangkat (meskipun pengguna logout di perangkat lama setelah mentransfer), dan pangkas kunci yang tidak berlaku berdasarkan TTL tidak aktif atau batas kunci per pengguna.

Menghapus kunci pemulihan

Credential Manager bersifat stateless dan tidak mengetahui aktivitas pengguna, sehingga tidak otomatis menghapus kunci pemulihan setelah digunakan. Untuk menghapus kunci pemulihan, panggil metode clearCredentialState(). Demi keamanan, hapus kunci setiap kali pengguna logout agar mereka tidak otomatis login kembali saat aplikasi diluncurkan berikutnya.

Kunci pemulihan hanya dihapus dalam situasi berikut:

  • Tindakan tingkat sistem: Pengguna meng-uninstal aplikasi atau menghapus datanya.
  • Panggilan tingkat aplikasi: Hapus kunci secara terprogram dengan memanggil clearCredentialState() saat menangani logout pengguna dalam kode aplikasi Anda.

Karena ClearCredentialStateRequest() default hanya menghapus status sesi standar dan membiarkan kunci pemulihan tetap utuh, lakukan penghapusan ganda saat logout:

  1. Panggil credentialManager.clearCredentialState(ClearCredentialStateRequest()) untuk menghapus status sesi penyedia kredensial yang aktif.
  2. Panggil credentialManager.clearCredentialState() dengan ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL) untuk menghapus RestoreCredential yang disimpan.
  3. Hapus penanda has_synced_restore_credential lokal Anda dari penyimpanan lokal.
// Create a ClearCredentialStateRequest object
val clearRequest = ClearCredentialStateRequest(TYPE_CLEAR_RESTORE_CREDENTIAL)

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

Langkah berikutnya