Logowanie użytkownika za pomocą Menedżera danych logowania

Credential Manager to interfejs API Jetpack, który obsługuje wiele metod logowania, takich jak nazwa użytkownika i hasło, klucze dostępu oraz sfederowane rozwiązania logowania (np. Zaloguj się przez Google) w jednym interfejsie API, co upraszcza integrację dla programistów.

Poza tym Menedżer danych logowania ujednolica interfejs logowania z różnymi metodami uwierzytelniania, dzięki czemu logowanie się w aplikacjach jest bardziej przejrzyste i łatwe, niezależnie od wybranej metody.

Na tej stronie objaśniamy pojęcie kluczy dostępu i etapy wdrażania obsługi po stronie klienta obsługi rozwiązań uwierzytelniających, w tym kluczy dostępu, za pomocą interfejsu Credential Manager API. Możesz też skorzystać z osobnej strony z najczęstszymi pytaniami, która zawiera odpowiedzi na bardziej szczegółowe i konkretne pytania.

Twoja opinia ma duże znaczenie w ulepszaniu interfejsu Credential Manager API. Jeśli napotkasz jakieś problemy lub pomysły na ulepszenie interfejsu API, możesz je zgłosić, korzystając z tego linku:

Przesyłanie opinii

Informacje o kluczach dostępu

Klucze dostępu to bezpieczniejsza i łatwiejsza w obsłudze metoda wymiany haseł. Dzięki kluczom dostępu użytkownicy mogą logować się w aplikacjach i na stronach internetowych za pomocą czujnika biometrycznego (np. czytnika odcisków palców lub rozpoznawania twarzy), kodu PIN lub wzoru. Dzięki temu logowanie się przebiega bezproblemowo, a użytkownicy nie muszą zapamiętywać nazw użytkowników i haseł.

Klucze dostępu wykorzystują WebAuthn (Uwierzytelnianie internetowe) – standard opracowany wspólnie przez FIDO Alliance i World Wide Web Consortium (W3C). WebAuthn uwierzytelnia użytkownika za pomocą kryptografii klucza publicznego. Strona lub aplikacja, w której użytkownik się loguje, może zobaczyć i zapisać klucz publiczny, ale nigdy nie będzie mieć klucza prywatnego. Klucz prywatny jest przechowywany w tajemnicy i bezpieczny. Klucz jest unikalny i powiązany ze stroną lub aplikacją, dlatego nie można go wyłudzić, co zwiększa bezpieczeństwo.

Menedżer danych logowania umożliwia użytkownikom tworzenie kluczy dostępu i przechowywanie ich w Menedżerze haseł Google.

Wymagania wstępne

Aby używać Menedżera danych logowania, wykonaj czynności opisane w tej sekcji.

Użyj najnowszej wersji platformy

Menedżer danych uwierzytelniających jest obsługiwany na urządzeniach z Androidem 4.4 (poziom interfejsu API 19) i nowszym.

Dodaj zależności do aplikacji

Dodaj te zależności do skryptu kompilacji modułu aplikacji:

Kotlin

dependencies {
    implementation("androidx.credentials:credentials:1.3.0-alpha03")

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

Odlotowy

dependencies {
    implementation "androidx.credentials:credentials:1.3.0-alpha03"

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

Zachowaj klasy w pliku ProGuard

W pliku proguard-rules.pro modułu dodaj te dyrektywy:

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

Dowiedz się więcej o zmniejszaniu, zaciemnianiu i optymalizowaniu aplikacji.

Dodaj obsługę Digital Asset Links

Aby włączyć obsługę kluczy dostępu w przypadku aplikacji na Androida, powiąż ją ze stroną internetową, której właścicielem jest ta aplikacja. Możesz zadeklarować to powiązanie, wykonując te czynności:

  1. Utwórz plik JSON protokołu Digital Asset Links. Aby na przykład zadeklarować, że witryna https://signin.example.com i aplikacja na Androida o nazwie pakietu com.example mogą udostępniać dane logowania, utwórz plik o nazwie assetlinks.json z tą zawartością:

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

    Pole relation to tablica z co najmniej jednym ciągiem znaków opisującym deklarowaną relację. Aby zadeklarować, że aplikacje i witryny współużytkują dane logowania, określ relacje jako delegate_permission/handle_all_urls i delegate_permission/common.get_login_creds.

    Pole target to obiekt określający zasób, którego dotyczy deklaracja. Te pola określają witrynę:

    namespace web
    site

    Adres URL witryny w formacie https://domain[:optional_port], np. https://www.example.com.

    domain musi być w pełni kwalifikowana, a optional_port musi być pominięty, gdy używasz portu 443 na potrzeby HTTPS.

    Miejsce docelowe site może być tylko domeną główną: nie można ograniczyć powiązania aplikacji do konkretnego podkatalogu. Nie umieszczaj w adresie URL ścieżki, np. ukośnika.

    Subdomeny nie są uznawane za pasujące: oznacza to, że jeśli określisz właściwość domain jako www.example.com, domena www.counter.example.com nie będzie powiązana z Twoją aplikacją.

    Te pola identyfikują aplikację na Androida:

    namespace android_app
    package_name Nazwa pakietu zadeklarowana w pliku manifestu aplikacji. Przykład: com.example.android
    sha256_cert_fingerprints Odciski cyfrowe SHA256 certyfikatu podpisywania Twojej aplikacji.
  2. Umieść plik JSON Link Digital Assets Link w tej lokalizacji w domenie logowania:

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

    Jeśli na przykład domena logowania to signin.example.com, hostuj plik JSON pod adresem https://signin.example.com/.well-known/assetlinks.json.

    Typ MIME pliku Digital Assets Link musi być w formacie JSON. Upewnij się, że serwer wysyła w odpowiedzi nagłówek Content-Type: application/json.

  3. Upewnij się, że dostawca hostingu zezwala Google na pobranie Twojego pliku Digital Asset Link. Jeśli masz plik robots.txt, musi on pozwalać agentowi Googlebot na pobranie /.well-known/assetlinks.json. Większość witryn może zezwalać na pobieranie plików ze ścieżki /.well-known/, aby inne usługi miały dostęp do metadanych w tych plikach:

    User-agent: *
    Allow: /.well-known/
    
  4. Dodaj ten wiersz do pliku manifestu w sekcji <application>:

    <meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
    
  5. Jeśli logujesz się przy użyciu hasła za pomocą Menedżera danych logowania, wykonaj ten krok, aby skonfigurować łączenie zasobów cyfrowych w pliku manifestu. Ten krok nie jest wymagany, jeśli używasz tylko kluczy dostępu.

    Zadeklaruj powiązanie w aplikacji na Androida. Dodaj obiekt określający pliki assetlinks.json do wczytania. Musisz zmienić znaczenie apostrofów i cudzysłowów użytych w ciągu znaków. Na przykład:

    <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
    

Konfigurowanie menedżera danych logowania

Aby skonfigurować i zainicjować obiekt CredentialManager, dodaj logikę podobną do tej:

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)

Wskazywanie pól danych logowania

W Androidzie 14 i nowszych możesz używać atrybutu isCredential do wskazywania pól danych logowania, takich jak pola nazwy użytkownika lub hasła. Ten atrybut wskazuje, że ten widok jest polem danych logowania, które może działać z Menedżerem danych logowania i zewnętrznymi dostawcami danych logowania, jednocześnie pomagając w ten sposób zwiększać dokładność sugestii autouzupełniania w usługach autouzupełniania. Gdy aplikacja korzysta z interfejsu Credential Manager API, wyświetlana jest dolna plansza Menedżera danych logowania z dostępnymi danymi logowania i nie trzeba już pokazywać okna autouzupełniania nazwy użytkownika ani hasła. W podobny sposób nie ma potrzeby pokazywania okna zapisywania autouzupełniania haseł, ponieważ aplikacja poprosi o zapisanie danych logowania do interfejsu Credential Manager API.

Aby użyć atrybutu isCredential, dodaj go do odpowiednich widoków danych:

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

Logowanie użytkownika

Aby pobrać wszystkie opcje klucza i hasła powiązane z kontem użytkownika, wykonaj te czynności:

  1. Zainicjuj opcje uwierzytelniania hasła i klucza:

    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. Użyj opcji pobranych z poprzedniego kroku, aby utworzyć żądanie logowania.

    Kotlin

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

    Java

    GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder()
        .addCredentialOption(getPasswordOption)
        .addCredentialOption(getPublicKeyCredentialOption)
        .build();
  3. Uruchom proces logowania:

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

Z przykładu poniżej dowiesz się, jak sformatować żądanie JSON po otrzymaniu klucza dostępu:

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

Z przykładu poniżej możesz zobaczyć, jak może wyglądać odpowiedź JSON po uzyskaniu danych logowania klucza publicznego:

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

Obsługuj wyjątki, gdy nie ma dostępnych danych logowania

W niektórych przypadkach użytkownik może nie mieć żadnych danych logowania lub nie wyrazi zgody na ich używanie. Jeśli funkcja getCredential() zostanie wywołana, ale nie zostaną znalezione żadne dane uwierzytelniające, zostanie zwrócony NoCredentialException. W takim przypadku kod powinien obsługiwać wystąpienia 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);
}

W Androidzie 14 i nowszych możesz zmniejszyć opóźnienie, wyświetlając selektor konta, używając metody prepareGetCredential() przed wywołaniem getCredential().

Kotlin

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

Java

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

Metoda prepareGetCredential() nie wywołuje elementów interfejsu. Pomaga Ci tylko wykonać prace przygotowawcze, by później uruchomić pozostałą operację pobierania danych logowania (co obejmuje interfejsy użytkownika) za pomocą interfejsu getCredential() API.

Dane z pamięci podręcznej są zwracane w obiekcie PrepareGetCredentialResponse. Jeśli są już dane logowania, wyniki zostaną zapisane w pamięci podręcznej. Możesz później uruchomić pozostały interfejs API getCredential(), aby wyświetlić selektor kont z danymi z pamięci podręcznej.

Procesy rejestracji

Użytkownika do uwierzytelniania możesz zarejestrować za pomocą klucza lub hasła.

Tworzenie kluczy dostępu

Aby dać użytkownikom możliwość zarejestrowania klucza dostępu i użycia go do ponownego uwierzytelniania, zarejestruj dane logowania użytkownika za pomocą obiektu 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());
                }
            }
        }
    );
}

Formatowanie żądania JSON

Po utworzeniu klucza dostępu musisz go powiązać z kontem użytkownika i zapisać go na serwerze. Poniższy przykładowy kod pokazuje, jak sformatować żądanie JSON podczas tworzenia klucza dostępu.

Z tego posta na blogu dotyczącego bezproblemowego uwierzytelniania w aplikacjach dowiesz się, jak sformatować żądanie JSON podczas tworzenia kluczy dostępu i uwierzytelniania za pomocą kluczy dostępu. Wyjaśnia także, dlaczego hasła nie są skutecznym rozwiązaniem do uwierzytelniania, jak używać dotychczasowych danych logowania biometrycznych, jak powiązać aplikację ze swoją stroną internetową, jak tworzyć klucze dostępu i jak uwierzytelniać się za pomocą kluczy dostępu.

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

Ustaw wartości dla elementu Authenticator

Parametr authenticatorAttachment można ustawić tylko podczas tworzenia danych logowania. Możesz podać platform, cross-platform lub brak wartości. W większości przypadków nie jest zalecana żadna wartość.

  • platform: aby zarejestrować bieżące urządzenie użytkownika lub poprosić użytkownika o przejście na klucze dostępu po zalogowaniu, ustaw authenticatorAttachment na platform.
  • cross-platform: ta wartość jest zwykle używana podczas rejestrowania danych logowania do uwierzytelniania wielopoziomowego i nie jest używana w kontekście klucza dostępu.
  • Brak wartości: aby zapewnić użytkownikom swobodę tworzenia kluczy dostępu na preferowanych urządzeniach (np. w ustawieniach konta), parametru authenticatorAttachment nie należy określać, gdy użytkownik decyduje się dodać klucz dostępu. W większości przypadków najlepszym rozwiązaniem jest nieokreślenie parametru.

Zapobieganie tworzeniu zduplikowanych kluczy dostępu

Wpisz identyfikatory danych logowania w opcjonalnej tablicy excludeCredentials, aby zapobiec tworzeniu nowego klucza dostępu, jeśli taki istnieje już u tego samego dostawcy kluczy.

Obsługa odpowiedzi JSON

Poniższy fragment kodu zawiera przykładową odpowiedź JSON, która pozwala utworzyć dane logowania klucza publicznego. Dowiedz się więcej o tym, jak postępować ze zwróconymi danymi logowania klucza publicznego.

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

Zweryfikuj źródło z danych klienta w formacie JSON

origin reprezentuje aplikację lub witrynę, z której pochodzi żądanie, i jest używany przez klucze dostępu do ochrony przed atakami phishingowymi. Serwer Twojej aplikacji musi sprawdzić, czy źródło danych klienckich znajduje się na liście dozwolonych zatwierdzonych aplikacji i witryn. Jeśli serwer otrzyma żądanie z aplikacji lub witryny z nierozpoznanego źródła, powinno ono zostać odrzucone.

W przypadku witryny origin wskazuje z tej samej witryny, z której dane logowania zostały użyte. Na przykład w przypadku adresu URL https://www.example.com:8443/store?category=shoes#athletic origin to https://www.example.com:8443.

W przypadku aplikacji na Androida klient użytkownika automatycznie ustawia origin na podpis aplikacji wywołującej. Ten podpis powinien zostać zweryfikowany jako dopasowanie na serwerze, aby można było zweryfikować element wywołujący interfejs API klucza dostępu. origin Androida to identyfikator URI pochodzący z skrótu SHA-256 certyfikatu podpisywania pakietu APK, np.:

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

Skróty SHA-256 certyfikatów podpisywania z magazynu kluczy można znaleźć, uruchamiając to polecenie terminala:

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

Skróty SHA-256 mają format szesnastkowy rozdzielany dwukropkiem (91:F7:CB:F9:D6:81…), a wartości origin w Androidzie są zakodowane w formacie base64url. Ten przykład w Pythonie pokazuje, jak przekonwertować format skrótu na zgodny format szesnastkowy rozdzielany dwukropkami:

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('=', ''))

Zastąp wartość fingerprint własną wartością. Oto przykładowy wynik:

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

Następnie możesz dopasować ten ciąg jako dozwolone źródło na Twoim serwerze. Jeśli masz wiele certyfikatów podpisywania, np. do debugowania i publikowania, lub wiele aplikacji, powtórz ten proces i zaakceptuj je wszystkie jako prawidłowe na serwerze.

Zapisywanie hasła użytkownika

Jeśli użytkownik poda nazwę i hasło na potrzeby procesu uwierzytelniania w aplikacji, możesz zarejestrować jego dane logowania, które umożliwiają uwierzytelnianie. Aby to zrobić, utwórz obiekt 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);
            }
        }
    );
}

Odzyskiwanie danych logowania

Jeśli użytkownik utraci dostęp do urządzenia, na którym przechowujeł swoje dane logowania, może być konieczne przywrócenie danych z bezpiecznej kopii zapasowej online. Więcej informacji o procesie odzyskiwania danych logowania znajdziesz w sekcji „Odzyskiwanie dostępu lub dodawanie nowych urządzeń” w tym poście na blogu: Bezpieczeństwo kluczy dostępu w Menedżerze haseł Google.

Dodaj obsługę narzędzi do zarządzania hasłami z dobrze znanymi adresami URL punktów końcowych kluczy dostępu

Aby zapewnić płynną integrację i przyszłą zgodność z narzędziami do zarządzania hasłami i danymi logowania, zalecamy dodanie obsługi dobrze znanych adresów URL punktów końcowych kluczy dostępu. Jest to otwarty protokół dla zgodnych stron, który umożliwia formalne informowanie o obsłudze kluczy dostępu i udostępnianie bezpośrednich linków do rejestracji kluczy dostępu i zarządzania nimi.

  1. W przypadku jednostki uzależnionej w domenie https://example.com, która ma witrynę oraz aplikacje na Androida i iOS, dobrze znanym adresem URL jest https://example.com/.well-known/passkey-endpoints.
  2. Gdy wysyłasz zapytanie o adres URL, odpowiedź powinna używać tego schematu

    {
      "enroll": "https://example.com/account/manage/passkeys/create"
      "manage": "https://example.com/account/manage/passkeys"
    }
    
  3. Aby link otwierał się bezpośrednio w aplikacji, a nie w przeglądarce, użyj linków aplikacji na Androida.

  4. Więcej informacji znajdziesz w wyjaśnieniu na temat znanego adresu URL punktów końcowych kluczy dostępu na GitHubie.

Rozwiązywanie najczęstszych błędów

W tabeli poniżej znajdziesz kilka typowych kodów błędów oraz ich opisy, a także informacje o ich przyczynach:

Kod błędu i opis Przyczyna
Podczas rozpoczynania logowania wystąpił błąd: 16: rozmówca został tymczasowo zablokowany z powodu zbyt wielu anulowanych próśb o zalogowanie.

Jeśli w trakcie tworzenia aplikacji napotkasz ten 24-godzinny okres oczekiwania, możesz go zresetować, czyszcząc pamięć aplikacji Usług Google Play.

Aby zmienić okres oczekiwania na urządzeniu testowym lub emulatorze, otwórz aplikację Telefon i wpisz ten kod: *#*#66382723#*#*. Aplikacja Telefon wyczyści wszystkie dane wejściowe i może zostać zamknięta, ale nie pojawi się komunikat z potwierdzeniem.

Podczas rozpoczynania logowania wystąpił błąd: 8: nieznany błąd wewnętrzny.
  1. Urządzenie nie jest prawidłowo skonfigurowane z kontem Google.
  2. Plik JSON klucza dostępu jest tworzony nieprawidłowo.
CreatePublicKeyCredentialDomException: Nie można zweryfikować przychodzącego żądania Identyfikator pakietu aplikacji nie jest zarejestrowany na Twoim serwerze. Sprawdź to w integracji po stronie serwera.
CreateCredentialUnknownException: Podczas zapisywania hasła po jednym kliknięciu znaleziono odpowiedź o niepowodzeniu hasła Ten błąd występuje tylko na Androidzie 13 i starszych wersjach i tylko wtedy, gdy dostawcą autouzupełniania jest Google. W takim przypadku użytkownicy zobaczą prośbę o zapisanie z autouzupełniania, a hasło zostanie zapisane w Menedżerze haseł Google. Pamiętaj, że dane logowania zapisane za pomocą autouzupełniania z Google są udostępniane dwukierunkowo przez interfejs Credential Manager API. W związku z tym ten błąd można bezpiecznie zignorować.

Dodatkowe materiały

Więcej informacji o interfejsie Credential Manager API i kluczach dostępu znajdziesz w tych materiałach: