Диспетчер учетных данных — это 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) и более поздних версиях.
Добавьте зависимости в ваше приложение
Добавьте следующие зависимости в сценарий сборки вашего модуля приложения:
Kotlin
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") }
Groovy
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, свяжите свое приложение с веб-сайтом, которым владеет ваше приложение. Вы можете объявить эту ассоциацию, выполнив следующие шаги:
Создайте 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 сертификата подписи вашего приложения. Разместите 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
.Убедитесь, что ваш хост разрешает Google получить ваш файл ссылки на цифровые активы. Если у вас есть файл
robots.txt
, он должен позволять агенту Googlebot получать/.well-known/assetlinks.json
. Большинство сайтов могут разрешить любому автоматизированному агенту извлекать файлы по пути/.well-known/
, чтобы другие службы могли получить доступ к метаданным в этих файлах:User-agent: * Allow: /.well-known/
Добавьте следующую строку в файл манифеста в разделе
<application>
:<meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
Если вы используете вход с паролем через диспетчер учетных данных, выполните этот шаг, чтобы настроить привязку цифровых активов в манифесте. Этот шаг не требуется, если вы используете только ключи доступа.
Объявите связь в приложении 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"
...
/>
Войдите в систему
Чтобы получить все параметры доступа и пароля, связанные с учетной записью пользователя, выполните следующие действия:
Инициализируйте параметры аутентификации по паролю и ключу доступа:
Котлин
// 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);
Используйте параметры, полученные на предыдущем шаге, для создания запроса на вход.
Котлин
val getCredRequest = GetCredentialRequest( listOf(getPasswordOption, getPublicKeyCredentialOption) )
Ява
GetCredentialRequest getCredRequest = new GetCredentialRequest.Builder() .addCredentialOption(getPasswordOption) .addCredentialOption(getPublicKeyCredentialOption) .build();
Запустите процесс входа:
Котлин
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-адресов конечных точек ключей доступа. Это открытый протокол, позволяющий связанным сторонам официально заявить о своей поддержке ключей доступа и предоставить прямые ссылки для регистрации ключей доступа и управления ими.
- Для проверяющей стороны
https://example.com
, у которой есть веб-сайт и приложения для Android и iOS, общеизвестным URL-адресом будетhttps://example.com/.well-known/passkey-endpoints
. При запросе URL-адреса ответ должен использовать следующую схему
{ "enroll": "https://example.com/account/manage/passkeys/create" "manage": "https://example.com/account/manage/passkeys" }
Чтобы эта ссылка открывалась непосредственно в вашем приложении, а не в Интернете, используйте ссылки приложений Android .
Более подробную информацию можно найти в известном пояснении URL-адресов конечных точек доступа на GitHub.
Помогите пользователям управлять своими ключами доступа, показав, какой поставщик их создал.
Одной из проблем, с которыми сталкиваются пользователи при управлении несколькими ключами доступа, связанными с данным приложением, является определение правильного ключа доступа для редактирования или удаления. Чтобы решить эту проблему, рекомендуется, чтобы приложения и веб-сайты включали дополнительную информацию, такую как поставщик, создавший учетные данные, дату создания и дату последнего использования, в список ключей доступа на экране настроек вашего приложения. Информацию о поставщике можно получить путем изучения AAGUID, связанный с соответствующим ключом доступа. AAGUID можно найти как часть данных аутентификации ключа доступа.
Например, если пользователь создает ключ доступа на устройстве под управлением Android с помощью диспетчера паролей Google, RP затем получает AAGUID, который выглядит примерно так: «ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4». Проверяющая сторона может добавить к ключу примечания в списке ключей, чтобы указать, что он был создан с помощью Диспетчера паролей Google.
Чтобы сопоставить AAGUID с поставщиком ключей доступа, RP могут использовать репозиторий AAGUID, созданный сообществом . Найдите AAGUID в списке , чтобы найти имя и значок поставщика ключей доступа.
Узнайте больше об интеграции AAGUID .
Устранение распространенных ошибок
В следующей таблице показаны несколько распространенных кодов и описаний ошибок, а также представлена некоторая информация об их причинах:
Код ошибки и описание | Причина |
---|---|
Ошибка входа в систему при начале: 16: вызывающий абонент был временно заблокирован из-за слишком большого количества отмененных запросов на вход. | Если вы столкнулись с этим 24-часовым периодом восстановления во время разработки, вы можете сбросить его, очистив хранилище приложений сервисов Google Play. Альтернативно, чтобы включить это время восстановления на тестовом устройстве или эмуляторе, перейдите в приложение Dialer и введите следующий код: |
Ошибка входа в систему при начале: 8: неизвестная внутренняя ошибка. |
|
CreatePublicKeyCredentialDomException: входящий запрос не может быть проверен | Идентификатор пакета приложения не зарегистрирован на вашем сервере. Проверьте это при интеграции на стороне сервера. |
CreateCredentialUnknownException: во время сохранения пароля обнаружен ответ на ошибку пароля одним нажатием. 16: пропуск сохранения пароля, поскольку пользователю, скорее всего, будет предложено автозаполнение Android. | Эта ошибка возникает только в Android 13 и более ранних версиях и только в том случае, если Google является поставщиком автозаполнения. В этом случае пользователи видят запрос на сохранение от автозаполнения, и пароль сохраняется в диспетчере паролей Google. Обратите внимание, что учетные данные, сохраненные с помощью автозаполнения Google, передаются в двустороннем порядке через Credential Manager API. В результате эту ошибку можно смело игнорировать. |
Дополнительные ресурсы
Чтобы узнать больше об API диспетчера учетных данных и ключах доступа, просмотрите следующие ресурсы:
- Руководство по использованию паролей
- Видео: как снизить зависимость от паролей в приложениях Android с поддержкой ключей доступа
- Codelab: узнайте, как упростить процедуру аутентификации с помощью API Credential Manager в вашем приложении для Android.
- Пример приложения: CredentialManager