Войдите в систему с помощью Credential Manager

Диспетчер учетных данных — это API Jetpack, который поддерживает несколько методов входа, таких как имя пользователя и пароль, ключи доступа и решения для федеративного входа (например, вход с помощью Google) в одном API, что упрощает интеграцию для разработчиков.

Кроме того, для пользователей Credential Manager унифицирует интерфейс входа для всех методов аутентификации, делая пользователям более понятным и простым вход в приложения независимо от выбранного ими метода.

На этой странице объясняется концепция ключей доступа и шаги по реализации поддержки решений аутентификации на стороне клиента, включая ключи доступа, с использованием API Credential Manager. Существует также отдельная страница часто задаваемых вопросов , на которой даны ответы на более подробные и конкретные вопросы.

Ваш отзыв — важная часть улучшения API Credential Manager. Поделитесь любыми обнаруженными проблемами или идеями по улучшению API, используя следующую ссылку:

Оставить отзыв

О ключах доступа

Ключи доступа — более безопасная и простая замена паролям. С помощью ключей доступа пользователи могут входить в приложения и на веб-сайты с помощью биометрического датчика (например, отпечатка пальца или распознавания лица), PIN-кода или рисунка. Это обеспечивает беспрепятственный вход в систему, освобождая ваших пользователей от необходимости запоминать имена пользователей и пароли.

Ключи доступа основаны на WebAuthn (веб-аутентификация), стандарте, совместно разработанном FIDO Alliance и Консорциумом World Wide Web (W3C). WebAuthn использует криптографию с открытым ключом для аутентификации пользователя. Веб-сайт или приложение, в которое входит пользователь, могут видеть и хранить открытый ключ, но не закрытый ключ. Закрытый ключ хранится в секрете и в безопасности. А поскольку ключ уникален и привязан к веб-сайту или приложению, ключи доступа не поддаются фишингу, что повышает безопасность.

Диспетчер учетных данных позволяет пользователям создавать пароли и хранить их в Диспетчере паролей Google .

Прочтите раздел Аутентификация пользователя с помощью ключей доступа , чтобы узнать, как реализовать простые процессы проверки подлинности с помощью ключей доступа с помощью Credential Manager.

Предварительные условия

Чтобы использовать диспетчер учетных данных, выполните действия, описанные в этом разделе.

Используйте последнюю версию платформы

Диспетчер учетных данных поддерживается на Android 4.4 (уровень API 19) и более поздних версиях.

Добавьте зависимости в ваше приложение

Добавьте следующие зависимости в сценарий сборки вашего модуля приложения:

Котлин

dependencies {
    implementation("androidx.credentials:credentials:1.5.0-alpha05")

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

классный

dependencies {
    implementation "androidx.credentials:credentials:1.5.0-alpha05"

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

Сохранять классы в файле ProGuard

В файл proguard-rules.pro вашего модуля добавьте следующие директивы:

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

Узнайте больше о том, как уменьшить, запутать и оптимизировать ваше приложение .

Добавить поддержку ссылок на цифровые активы.

Чтобы включить поддержку ключей доступа для вашего приложения Android, свяжите свое приложение с веб-сайтом, которым владеет ваше приложение. Вы можете объявить эту ассоциацию, выполнив следующие шаги:

  1. Создайте JSON-файл ссылок на цифровые активы. Например, чтобы объявить, что веб-сайт https://signin.example.com и приложение Android с именем пакета com.example могут использовать общие учетные данные для входа, создайте файл с именем assetlinks.json со следующим содержимым:

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

    Поле relation представляет собой массив из одной или нескольких строк, описывающих объявляемое отношение. Чтобы объявить, что приложения и сайты используют общие учетные данные для входа, укажите отношения как delegate_permission/handle_all_urls и delegate_permission/common.get_login_creds .

    target поле — это объект, определяющий актив, к которому применяется декларация. Следующие поля идентифицируют веб-сайт:

    namespace web
    site

    URL-адрес веб-сайта в формате https:// domain [: optional_port ] ; например, https://www.example.com .

    domain должен быть полностью определенным, а optional_port должен быть опущен при использовании порта 443 для HTTPS.

    Целевым site может быть только корневой домен: вы не можете ограничить связь приложения определенным подкаталогом. Не включайте в URL-адрес путь, например косую черту.

    Субдомены не считаются совпадающими: то есть, если вы укажете domain как www.example.com , домен www.counter.example.com не будет связан с вашим приложением.

    Следующие поля идентифицируют приложение Android:

    namespace android_app
    package_name Имя пакета, объявленное в манифесте приложения. Например, com.example.android
    sha256_cert_fingerprints Отпечатки SHA256 сертификата подписи вашего приложения.
  2. Разместите JSON-файл Digital Assets Link в следующем месте домена для входа:

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

    Например, если ваш домен для входа — signin.example.com , разместите файл JSON по адресу https://signin.example.com/.well-known/assetlinks.json .

    Тип MIME для файла ссылки на цифровые активы должен быть JSON. Убедитесь, что сервер отправляет в ответ заголовок Content-Type: application/json .

  3. Убедитесь, что ваш хост разрешает Google получить ваш файл ссылки на цифровые активы. Если у вас есть файл robots.txt , он должен позволять агенту Googlebot получать /.well-known/assetlinks.json . Большинство сайтов могут разрешить любому автоматизированному агенту извлекать файлы по пути /.well-known/ , чтобы другие службы могли получить доступ к метаданным в этих файлах:

    User-agent: *
    Allow: /.well-known/
    
  4. Добавьте следующую строку в файл манифеста в разделе <application> :

    <meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
    
  5. Если вы используете вход с паролем через диспетчер учетных данных, выполните этот шаг, чтобы настроить привязку цифровых активов в манифесте. Этот шаг не требуется, если вы используете только ключи доступа.

    Объявите связь в приложении Android. Добавьте объект, указывающий файлы assetlinks.json для загрузки. Вы должны избегать любых апострофов и кавычек, которые вы используете в строке. Например:

    <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
    

Настройте диспетчер учетных данных

Чтобы настроить и инициализировать объект CredentialManager , добавьте логику, аналогичную следующей:

Котлин

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

Ява

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

Укажите поля учетных данных

В Android 14 и более поздних версиях атрибут isCredential можно использовать для указания полей учетных данных, таких как поля имени пользователя или пароля. Этот атрибут указывает, что это представление представляет собой поле учетных данных, предназначенное для работы с диспетчером учетных данных и сторонними поставщиками учетных данных, а также помогает службам автозаполнения предоставлять более эффективные предложения по автозаполнению. Когда приложение использует API диспетчера учетных данных, отображается нижний лист диспетчера учетных данных с доступными учетными данными, и больше нет необходимости отображать диалоговое окно автозаполнения для имени пользователя или пароля. Аналогичным образом нет необходимости отображать диалоговое окно сохранения паролей автозаполнения, поскольку приложение запросит API диспетчера учетных данных для сохранения учетных данных.

Чтобы использовать атрибут isCredential , добавьте его в соответствующие представления:

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

Войдите в систему

Чтобы получить все параметры доступа и пароля, связанные с учетной записью пользователя, выполните следующие действия:

  1. Инициализируйте параметры аутентификации по паролю и ключу доступа:

    Котлин

    // 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
    )

    Ява

    // 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. Используйте параметры, полученные на предыдущем шаге, для создания запроса на вход.

    Котлин

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

    Ява

    GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder()
        .addCredentialOption(getPasswordOption)
        .addCredentialOption(getPublicKeyCredentialOption)
        .build();
  3. Запустите процесс входа:

    Котлин

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

    Ява

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

В следующем примере показано, как отформатировать запрос JSON при получении ключа доступа:

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

В следующем примере показано, как может выглядеть ответ JSON после получения учетных данных открытого ключа:

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

Обработка исключений, когда учетные данные недоступны.

В некоторых случаях у пользователя могут отсутствовать какие-либо учетные данные или пользователь может не дать согласие на использование доступных учетных данных. Если вызывается getCredential() и учетные данные не найдены, возвращается NoCredentialException . Если это произойдет, ваш код должен обрабатывать экземпляры NoCredentialException .

Котлин

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

Ява

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

В Android 14 или более поздней версии вы можете уменьшить задержку при отображении средства выбора учетной записи, используя метод prepareGetCredential() перед вызовом getCredential() .

Котлин

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

Ява

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

Метод prepareGetCredential() не вызывает элементы пользовательского интерфейса. Это только поможет вам выполнить подготовительную работу, чтобы позже можно было запустить оставшуюся операцию получения учетных данных (которая включает в себя пользовательский интерфейс) через API getCredential() .

Кэшированные данные возвращаются в объекте PrepareGetCredentialResponse . Если учетные данные уже существуют, результаты будут кэшированы, и позже вы сможете запустить оставшийся API getCredential() , чтобы вызвать селектор учетной записи с кэшированными данными.

Потоки регистрации

Вы можете зарегистрировать пользователя для аутентификации, используя ключ доступа или пароль .

Создать пароль

Чтобы предоставить пользователям возможность зарегистрировать ключ доступа и использовать его для повторной аутентификации, зарегистрируйте учетные данные пользователя с помощью объекта CreatePublicKeyCredentialRequest .

Котлин

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

Ява

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

Отформатируйте запрос JSON

После создания ключа доступа вы должны связать его с учетной записью пользователя и сохранить открытый ключ ключа доступа на своем сервере. В следующем примере кода показано, как форматировать запрос JSON при создании ключа доступа.

В этой записи блога о внедрении простой аутентификации в ваших приложениях показано, как форматировать запрос JSON при создании ключей доступа и при аутентификации с использованием ключей доступа. В нем также объясняется, почему пароли не являются эффективным решением для аутентификации, как использовать существующие биометрические учетные данные, как связать ваше приложение с принадлежащим вам веб-сайтом, как создавать ключи доступа и как проходить аутентификацию с использованием ключей доступа.

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

Установите значения для AuthenticatorAttachment

authenticatorAttachment можно задать только во время создания учетных данных. Вы можете указать platform , cross-platform или не указывать значение. В большинстве случаев не рекомендуется указывать какое-либо значение.

  • platform : чтобы зарегистрировать текущее устройство пользователя или предложить пользователю пароля перейти на ключи доступа после входа в систему, задайте для authenticatorAttachment значение platform .
  • cross-platform : это значение обычно используется при регистрации многофакторных учетных данных и не используется в контексте ключа доступа.
  • Нет значения . Чтобы предоставить пользователям возможность создавать ключи доступа на предпочитаемых ими устройствах (например, в настройках учетной записи), параметр authenticatorAttachment не должен указываться, когда пользователь решает добавить ключ доступа. В большинстве случаев лучше всего оставить параметр неуказанным.

Предотвращение создания дубликатов ключей доступа

Перечислите идентификаторы учетных данных в необязательном массиве excludeCredentials , чтобы предотвратить создание нового ключа доступа, если он уже существует у того же поставщика ключей доступа.

Обработка ответа JSON

В следующем фрагменте кода показан пример ответа JSON для создания учетных данных открытого ключа. Узнайте больше о том, как обращаться с возвращенными учетными данными открытого ключа .

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

Проверьте происхождение на основе данных клиента в формате JSON.

origin представляет собой приложение или веб-сайт, с которого поступает запрос, и используется ключами доступа для защиты от фишинговых атак. Сервер вашего приложения должен проверить источник данных клиента по белому списку одобренных приложений и веб-сайтов. Если сервер получает запрос от приложения или веб-сайта из неизвестного источника, запрос должен быть отклонен.

В случае с Интернетом origin отражает тот же источник сайта , на котором были выполнены входы в учетные данные. Например, для URL-адреса https://www.example.com:8443/store?category=shoes#athletic origin является https://www.example.com:8443 .

Для приложений Android пользовательский агент автоматически устанавливает origin подписи вызывающего приложения. Эту подпись следует проверить на совпадение на вашем сервере, чтобы проверить вызывающую сторону API ключа доступа. origin Android — это URI, полученный из хэша SHA-256 сертификата подписи APK, например:

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

Хэши SHA-256 сертификатов подписи из хранилища ключей можно найти, выполнив следующую команду терминала:

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

Хэши SHA-256 представлены в шестнадцатеричном формате, разделенном двоеточием ( 91:F7:CB:F9:D6:81… ), а origin значения Android закодированы в формате Base64url. В этом примере Python показано, как преобразовать формат хеш-функции в совместимый шестнадцатеричный формат, разделенный двоеточиями:

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

Замените значение fingerprint своим собственным значением. Вот пример результата:

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

Затем вы можете сопоставить эту строку как разрешенный источник на вашем сервере. Если у вас есть несколько сертификатов подписи, например сертификаты для отладки и выпуска, или несколько приложений, повторите процесс и примите все эти источники как действительные на сервере.

Сохраните пароль пользователя

Если пользователь предоставляет имя пользователя и пароль для потока аутентификации в вашем приложении, вы можете зарегистрировать учетные данные пользователя, которые можно будет использовать для аутентификации пользователя. Для этого создайте объект CreatePasswordRequest :

Котлин

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

Ява

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

Поддержка восстановления учетных данных

Если у пользователя больше нет доступа к устройству, на котором он хранил свои учетные данные, ему может потребоваться восстановление из безопасной онлайн-резервной копии. Чтобы узнать больше о поддержке этого процесса восстановления учетных данных, прочтите раздел «Восстановление доступа или добавление новых устройств» в этой записи блога: Безопасность паролей в диспетчере паролей Google .

Добавлена ​​поддержка инструментов управления паролями с использованием известных URL-адресов конечных точек паролей.

Для обеспечения плавной интеграции и будущей совместимости с инструментами управления паролями и учетными данными мы рекомендуем добавить поддержку общеизвестных URL-адресов конечных точек ключей доступа. Это открытый протокол, позволяющий связанным сторонам официально заявить о своей поддержке ключей доступа и предоставить прямые ссылки для регистрации ключей доступа и управления ими.

  1. Для проверяющей стороны https://example.com , у которой есть веб-сайт и приложения для Android и iOS, общеизвестным URL-адресом будет https://example.com/.well-known/passkey-endpoints .
  2. При запросе URL-адреса ответ должен использовать следующую схему

    {
      "enroll": "https://example.com/account/manage/passkeys/create"
      "manage": "https://example.com/account/manage/passkeys"
    }
    
  3. Чтобы эта ссылка открывалась непосредственно в вашем приложении, а не в Интернете, используйте ссылки приложений Android .

  4. Более подробную информацию можно найти в известном пояснении URL-адресов конечных точек доступа на GitHub.

Помогите пользователям управлять своими ключами доступа, показав, какой поставщик их создал.

Одной из проблем, с которыми сталкиваются пользователи при управлении несколькими ключами доступа, связанными с данным приложением, является определение правильного ключа доступа для редактирования или удаления. Чтобы решить эту проблему, рекомендуется, чтобы приложения и веб-сайты включали дополнительную информацию, такую ​​как поставщик, создавший учетные данные, дату создания и дату последнего использования, в список ключей доступа на экране настроек вашего приложения. Информацию о поставщике можно получить путем изучения AAGUID, связанный с соответствующим ключом доступа. AAGUID можно найти как часть данных аутентификации ключа доступа.

Например, если пользователь создает ключ доступа на устройстве под управлением Android с помощью диспетчера паролей Google, RP затем получает AAGUID, который выглядит примерно так: «ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4». Проверяющая сторона может добавить к ключу примечания в списке ключей, чтобы указать, что он был создан с помощью Диспетчера паролей Google.

Чтобы сопоставить AAGUID с поставщиком ключей доступа, RP могут использовать репозиторий AAGUID, созданный сообществом . Найдите AAGUID в списке , чтобы найти имя и значок поставщика ключей доступа.

Узнайте больше об интеграции AAGUID .

Устранение распространенных ошибок

В следующей таблице показаны несколько распространенных кодов и описаний ошибок, а также представлена ​​некоторая информация об их причинах:

Код ошибки и описание Причина
Ошибка входа в систему при начале: 16: вызывающий абонент был временно заблокирован из-за слишком большого количества отмененных запросов на вход.

Если вы столкнулись с этим 24-часовым периодом восстановления во время разработки, вы можете сбросить его, очистив хранилище приложений сервисов Google Play.

Альтернативно, чтобы включить это время восстановления на тестовом устройстве или эмуляторе, перейдите в приложение Dialer и введите следующий код: *#*#66382723#*#* . Приложение «Дозвон» очищает все введенные данные и может закрыться, но сообщение с подтверждением отсутствует.

Ошибка входа в систему при начале: 8: неизвестная внутренняя ошибка.
  1. На устройстве неправильно настроен аккаунт Google.
  2. Ключ доступа JSON создается неправильно.
CreatePublicKeyCredentialDomException: входящий запрос не может быть проверен Идентификатор пакета приложения не зарегистрирован на вашем сервере. Проверьте это при интеграции на стороне сервера.
CreateCredentialUnknownException: во время сохранения пароля обнаружен ответ на ошибку пароля одним нажатием. 16: пропуск сохранения пароля, поскольку пользователю, скорее всего, будет предложено автозаполнение Android. Эта ошибка возникает только в Android 13 и более ранних версиях и только в том случае, если Google является поставщиком автозаполнения. В этом случае пользователи видят запрос на сохранение от автозаполнения, и пароль сохраняется в диспетчере паролей Google. Обратите внимание, что учетные данные, сохраненные с помощью автозаполнения Google, передаются в двустороннем порядке через Credential Manager API. В результате эту ошибку можно смело игнорировать.

Дополнительные ресурсы

Чтобы узнать больше об API диспетчера учетных данных и ключах доступа, просмотрите следующие ресурсы: