1. Прежде чем начать
Традиционные решения аутентификации создают ряд проблем с безопасностью и удобством использования.
Пароли широко используются, но...
- Легко забывается
- Пользователям требуются знания для создания надежных паролей.
- Легко подделать, собрать и воспроизвести злоумышленникам.
Android работала над созданием Credential Manager API , чтобы упростить вход в систему и устранить риски безопасности, поддерживая ключи доступа — отраслевой стандарт нового поколения для аутентификации без пароля .
Диспетчер учетных данных объединяет поддержку ключей доступа и сочетает ее с традиционными методами аутентификации, такими как пароли, вход с помощью Google и т. д.
Пользователи смогут создавать ключи доступа, хранить их в диспетчере паролей Google, который будет синхронизировать эти ключи доступа на всех устройствах Android, на которых выполнен вход в систему. Необходимо создать ключ доступа, связать его с учетной записью пользователя и сохранить его открытый ключ. на сервере, прежде чем пользователь сможет войти с ним в систему.
В этой лаборатории кода вы узнаете, как зарегистрироваться с помощью ключей доступа и пароля с помощью Credential Manager API и использовать их для будущих целей аутентификации. Есть 2 потока, в том числе:
- Зарегистрируйтесь: используя ключи доступа и пароль.
- Войдите: используя ключи доступа и сохраненный пароль.
Предварительные условия
- Базовое понимание того, как запускать приложения в Android Studio.
- Базовое понимание процесса аутентификации в приложениях Android.
- Базовое понимание ключей доступа .
Что вы узнаете
- Как создать пароль.
- Как сохранить пароль в менеджере паролей.
- Как аутентифицировать пользователей с помощью ключа доступа или сохраненного пароля.
Что вам понадобится
Одна из следующих комбинаций устройств:
- Устройство Android под управлением Android 9 или более поздней версии (для ключей доступа) и Android 4.4 или более поздней версии (для аутентификации по паролю через Credential Manager API).
- Устройство желательно с биометрическим датчиком.
- Обязательно зарегистрируйте биометрию (или блокировку экрана).
- Версия плагина Kotlin: 1.8.10.
2. Настройте
- Клонируйте этот репозиторий на свой ноутбук из ветки credman_codelab : https://github.com/android/identity-samples/tree/credman_codelab.
git clone -b credman_codelab https://github.com/android/identity-samples.git
- Перейдите в модуль CredentialManager и откройте проект в Android Studio.
Давайте посмотрим исходное состояние приложения
Чтобы увидеть, как работает исходное состояние приложения, выполните следующие действия:
- Запустите приложение.
- Вы видите главный экран с кнопкой регистрации и входа. Эти кнопки пока ничего не делают, но мы включим их функции в следующих разделах.
3. Добавьте возможность регистрации с использованием ключей доступа.
При регистрации новой учетной записи в приложении Android, использующем Credential Manager API, пользователи могут создать ключ доступа для своей учетной записи. Этот ключ доступа будет надежно храниться у выбранного пользователем поставщика учетных данных и использоваться для будущих входов в систему, не требуя от пользователя каждый раз вводить свой пароль.
Теперь вы создадите ключ доступа и зарегистрируете учетные данные пользователя, используя биометрию/блокировку экрана.
Зарегистрируйтесь с помощью пароля
Код внутри Credential Manager/app/main/java/SignUpFragment.kt определяет текстовое поле «имя пользователя» и кнопку для регистрации с помощью ключа доступа.
Передайте вызов и другой ответ JSON на вызов createPasskey().
Прежде чем ключ доступа будет создан, вам необходимо запросить у сервера необходимую информацию, которая будет передана в Credential Manager API во время вызова createCredential ().
В ресурсах вашего проекта уже есть макет ответа под названием RegFromServer.txt , который возвращает необходимые параметры в этой лаборатории кода.
- В своем приложении перейдите к SignUpFragment.kt и найдите метод SignUpWithPasskeys , в котором вы напишете логику создания ключа доступа и входа пользователя. Этот метод можно найти в том же классе.
- Проверьте блок else с комментарием для вызова
createPasskey()
и замените его следующим кодом:
SignUpFragment.kt
//TODO : Call createPasskey() to signup with passkey
val data = createPasskey()
Этот метод будет вызван, как только на экране появится действительное имя пользователя.
- Внутри метода
createPasskey()
вам необходимо создатьCreatePublicKeyCredentialRequest()
с возвращаемыми необходимыми параметрами.
SignUpFragment.kt
//TODO create a CreatePublicKeyCredentialRequest() with necessary registration json from server
val request = CreatePublicKeyCredentialRequest(fetchRegistrationJsonFromServer())
Метод fetchRegistrationJsonFromServer()
считывает ответ JSON эмулируемого сервера PublicKeyCredentialCreationOptions
из ресурсов и возвращает регистрационный JSON, который необходимо передать при создании ключа доступа.
- Найдите метод
fetchRegistrationJsonFromServer()
и замените TODO следующим кодом, чтобы вернуть JSON, а также удалите оператор возврата пустой строки:
SignUpFragment.kt
//TODO fetch registration mock response
val response = requireContext().readFromAsset("RegFromServer")
//Update userId,challenge, name and Display name in the mock
return response.replace("<userId>", getEncodedUserId())
.replace("<userName>", binding.username.text.toString())
.replace("<userDisplayName>", binding.username.text.toString())
.replace("<challenge>", getEncodedChallenge())
- Этот JSON неполный и содержит 4 поля, которые необходимо заменить.
- UserId должен быть уникальным, чтобы пользователь мог создать несколько ключей доступа (при необходимости). Замените
<userId>
сгенерированным значениемuserId
. -
<challenge>
также должен быть уникальным, чтобы вы могли создать случайное уникальное испытание. Этот метод уже есть в вашем коде.
Ответ реального сервера PublicKeyCredentialCreationOptions
может возвращать дополнительные параметры. Пример некоторых из этих полей приведен ниже:
{
"challenge": String,
"rp": {
"name": String,
"id": String
},
"user": {
"id": String,
"name": String,
"displayName": String
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": 1800000,
"attestation": "none",
"excludeCredentials": [],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"requireResidentKey": true,
"residentKey": "required",
"userVerification": "required"
}
}
В следующей таблице поясняются некоторые важные параметры объекта PublicKeyCredentialCreationOptions
:
Параметры | Описания |
Генерируемая сервером случайная строка, содержащая достаточно энтропии, чтобы угадать ее было невозможно. Его длина должна быть не менее 16 байт. Это необходимо, но не используется во время регистрации, если не проводится аттестация . | |
Уникальный идентификатор пользователя. Это значение не должно включать личную информацию, например адреса электронной почты или имена пользователей. Случайное 16-байтовое значение, созданное для каждой учетной записи, подойдет. | |
Это поле должно содержать уникальный идентификатор учетной записи, который узнает пользователь, например адрес электронной почты или имя пользователя. Это будет отображаться в средстве выбора учетной записи. (При использовании имени пользователя используйте то же значение, что и при аутентификации по паролю.) | |
Это поле является необязательным и более удобным для пользователя именем учетной записи. | |
Объект проверяющей стороны соответствует данным вашего приложения. Он имеет следующие атрибуты:
| |
Список разрешенных алгоритмов и типов ключей. Этот список должен содержать хотя бы один элемент. | |
Пользователь, пытающийся зарегистрировать устройство, возможно, зарегистрировал другие устройства. Чтобы ограничить создание нескольких учетных данных для одной и той же учетной записи на одном аутентификаторе, вы можете игнорировать эти устройства. Член | |
Указывает, следует ли прикреплять устройство к платформе или нет, или нет необходимости в этом. Установите это значение на | |
| укажите значение, |
Создать учетные данные
- После создания
CreatePublicKeyCredentialRequest()
вам необходимо вызвать вызовcreateCredential()
с созданным запросом.
SignUpFragment.kt
//TODO call createCredential() with createPublicKeyCredentialRequest
try {
response = credentialManager.createCredential(
requireActivity(),
request
) as CreatePublicKeyCredentialResponse
} catch (e: CreateCredentialException) {
configureProgress(View.INVISIBLE)
handlePasskeyFailure(e)
}
- Вы передаете необходимую информацию в
createCredential()
. - Как только запрос будет успешным, вы увидите нижнюю таблицу на экране, предлагающую создать ключ доступа.
- Теперь пользователи могут подтвердить свою личность с помощью биометрии, блокировки экрана и т. д.
- Вы управляете видимостью отображаемых представлений и обрабатываете исключения, если запрос по какой-либо причине завершается неудачей или неудачей. Здесь сообщения об ошибках регистрируются и отображаются в приложении в диалоговом окне ошибок. Вы можете проверить полные журналы ошибок через студию Android или команду
adb debug
.
- Наконец, вам необходимо завершить процесс регистрации. Приложение отправляет учетные данные открытого ключа на сервер, который регистрирует его для текущего пользователя.
Здесь мы использовали макетный сервер, поэтому мы просто возвращаем true, указывая, что сервер сохранил зарегистрированный открытый ключ для будущих целей аутентификации и проверки. Вы можете узнать больше о регистрации ключей доступа на стороне сервера для вашей собственной реализации.
Внутри метода signUpWithPasskeys()
найдите соответствующий комментарий и замените его следующим кодом:
SignUpFragment.kt
//TODO : complete the registration process after sending public key credential to your server and let the user in
data?.let {
registerResponse()
DataProvider.setSignedInThroughPasskeys(true)
listener.showHome()
}
-
registerResponse()
возвращаетtrue
, указывая, что макетный сервер сохранил открытый ключ для использования в будущем. - Установите для флага
setSignedInThroughPasskeys
значениеtrue
. - После входа в систему вы перенаправляете пользователя на главный экран.
Настоящий PublicKeyCredential
может содержать больше полей. Пример этих полей показан ниже:
{
"id": String,
"rawId": String,
"type": "public-key",
"response": {
"clientDataJSON": String,
"attestationObject": String,
}
}
В следующей таблице поясняются некоторые важные параметры объекта PublicKeyCredential
:
Параметры | Описания |
Идентификатор созданного ключа доступа в кодировке Base64URL. Этот идентификатор помогает браузеру определить, имеется ли на устройстве соответствующий ключ доступа при аутентификации. Это значение должно храниться в базе данных на серверной стороне. | |
Версия идентификатора учетных данных объекта | |
Объект | |
Объект аттестации в кодировке |
Запустите приложение, и вы сможете нажать кнопку «Зарегистрироваться с ключами доступа» и создать ключ доступа.
4. Сохраните пароль в поставщике учетных данных.
В этом приложении на экране регистрации у вас уже есть регистрация с именем пользователя и паролем, реализованная в демонстрационных целях.
Чтобы сохранить учетные данные пароля пользователя у его поставщика паролей, вы реализуете CreatePasswordRequest
для передачи в createCredential()
для сохранения пароля.
- Найдите метод
signUpWithPassword()
, замените TODO на вызовcreatePassword
:
SignUpFragment.kt
//TODO : Save the user credential password with their password provider
createPassword()
- Внутри метода
createPassword()
вам необходимо создать запрос пароля следующим образом, замените TODO следующим кодом:
SignUpFragment.kt
//TODO : CreatePasswordRequest with entered username and password
val request = CreatePasswordRequest(
binding.username.text.toString(),
binding.password.text.toString()
)
- Затем внутри метода
createPassword()
создайте учетные данные с помощью запроса на создание пароля и сохраните учетные данные пароля пользователя у своего поставщика паролей. Замените TODO следующим кодом:
SignUpFragment.kt
//TODO : Create credential with created password request
try {
credentialManager.createCredential(requireActivity(), request) as CreatePasswordResponse
} catch (e: Exception) {
Log.e("Auth", " Exception Message : " + e.message)
}
- Теперь вы успешно сохранили учетные данные пароля у поставщика паролей пользователя для аутентификации с помощью пароля всего одним касанием.
5. Добавьте возможность аутентификации с помощью ключа доступа или пароля.
Теперь вы готовы использовать его как способ безопасной аутентификации в своем приложении.
Получите вызов и другие параметры для передачи вызову getPasskey().
Прежде чем попросить пользователя пройти аутентификацию, вам необходимо запросить параметры для передачи WebAuthn JSON с сервера, включая запрос.
В ваших ресурсах уже есть макет ответа ( AuthFromServer.txt ), который возвращает такие параметры в этой кодовой лаборатории.
- В своем приложении перейдите к SignInFragment.kt, найдите метод
signInWithSavedCredentials
, в котором вы напишете логику аутентификации с помощью сохраненного ключа доступа или пароля, и разрешите пользователю войти: - Проверьте блок else с комментарием для вызова
createPasskey()
и замените его следующим кодом:
SignInFragment.kt
//TODO : Call getSavedCredentials() method to signin using passkey/password
val data = getSavedCredentials()
- Внутри метода getSavedCredentials() вам необходимо создать
GetPublicKeyCredentialOption()
с необходимыми параметрами, необходимыми для получения учетных данных от вашего поставщика учетных данных.
SigninFragment.kt
//TODO create a GetPublicKeyCredentialOption() with necessary registration json from server
val getPublicKeyCredentialOption =
GetPublicKeyCredentialOption(fetchAuthJsonFromServer(), null)
Метод fetchAuthJsonFromServer()
считывает ответ JSON аутентификации из ресурсов и возвращает JSON аутентификации для получения всех ключей доступа, связанных с этой учетной записью пользователя.
Вторым параметром GetPublicKeyCredentialOption() является clientDataHash
— хеш, который используется для проверки личности проверяющей стороны. Установите это значение, только если вы установили GetCredentialRequest.origin
. В примере приложения для этого параметра установлено значение null
.
- Найдите метод fetchAuthJsonFromServer() и замените TODO следующим кодом, чтобы вернуть json, а также удалите оператор возврата пустой строки:
SignInFragment.kt
//TODO fetch authentication mock json
return requireContext().readFromAsset("AuthFromServer")
Примечание. Сервер этой кодовой лаборатории предназначен для возврата JSON, максимально похожего на словарь PublicKeyCredentialRequestOptions
, который передается в вызов API getCredential(). Следующий фрагмент кода включает несколько примеров параметров, которые вы можете получить в реальном ответе:
{
"challenge": String,
"rpId": String,
"userVerification": "",
"timeout": 1800000
}
В следующей таблице поясняются некоторые важные параметры объекта PublicKeyCredentialRequestOptions
:
Параметры | Описания |
Созданный сервером вызов в объекте | |
Идентификатор RP — это домен. Веб-сайт может указать либо свой домен, либо регистрируемый суффикс . Это значение должно соответствовать параметру |
- Затем вам необходимо создать объект
PasswordOption()
для получения всех сохраненных паролей, сохраненных в вашем поставщике паролей, через API диспетчера учетных данных для этой учетной записи пользователя. Внутри методаgetSavedCredentials()
найдите TODO и замените его следующим:
SigninFragment.kt
//TODO create a PasswordOption to retrieve all the associated user's password
val getPasswordOption = GetPasswordOption()
Получить учетные данные
- Далее вам нужно вызвать запрос
getCredential()
со всеми вышеперечисленными параметрами, чтобы получить связанные учетные данные:
SignInFragment.kt
//TODO call getCredential() with required credential options
val result = try {
credentialManager.getCredential(
requireActivity(),
GetCredentialRequest(
listOf(
getPublicKeyCredentialOption,
getPasswordOption
)
)
)
} catch (e: Exception) {
configureViews(View.INVISIBLE, true)
Log.e("Auth", "getCredential failed with exception: " + e.message.toString())
activity?.showErrorAlert(
"An error occurred while authenticating through saved credentials. Check logs for additional details"
)
return null
}
if (result.credential is PublicKeyCredential) {
val cred = result.credential as PublicKeyCredential
DataProvider.setSignedInThroughPasskeys(true)
return "Passkey: ${cred.authenticationResponseJson}"
}
if (result.credential is PasswordCredential) {
val cred = result.credential as PasswordCredential
DataProvider.setSignedInThroughPasskeys(false)
return "Got Password - User:${cred.id} Password: ${cred.password}"
}
if (result.credential is CustomCredential) {
//If you are also using any external sign-in libraries, parse them here with the utility functions provided.
}
- Вы передаете необходимую информацию в
getCredential()
. Для этого требуется список параметров учетных данных и контекст действия для отображения параметров на нижнем листе в этом контексте. - Как только запрос будет успешным, вы увидите на экране нижнюю таблицу со списком всех созданных учетных данных для связанной учетной записи.
- Теперь пользователи могут подтвердить свою личность с помощью биометрии, блокировки экрана и т. д. для аутентификации выбранных учетных данных.
- Если выбранные учетные данные являются
PublicKeyCredential
, установите для флагаsetSignedInThroughPasskeys
значениеtrue
. В противном случае установите значениеfalse
.
Следующий фрагмент кода включает пример объекта PublicKeyCredential
:
{
"id": String
"rawId": String
"type": "public-key",
"response": {
"clientDataJSON": String
"authenticatorData": String
"signature": String
"userHandle": String
}
}
Следующая таблица не является исчерпывающей, но она содержит важные параметры объекта PublicKeyCredential
:
Параметры | Описания |
Идентификатор аутентифицированного ключа доступа в кодировке Base64URL. | |
Версия идентификатора учетных данных объекта | |
Объект | |
Объект | |
Объект подписи | |
Объект |
- Наконец, вам необходимо завершить процесс аутентификации. Обычно после того, как пользователь завершает аутентификацию с помощью пароля, приложение отправляет учетные данные открытого ключа, содержащие утверждение аутентификации , на сервер, который проверяет утверждение и аутентифицирует пользователя.
Здесь мы использовали макетный сервер, поэтому мы просто возвращаем true
, указывая, что сервер проверил утверждение. Вы можете узнать больше об аутентификации по ключу доступа на стороне сервера для вашей собственной реализации.
Внутри метода signInWithSavedCredentials()
найдите соответствующий комментарий и замените его следующим кодом:
SignInFragment.kt
//TODO : complete the authentication process after validating the public key credential to your server and let the user in.
data?.let {
sendSignInResponseToServer()
listener.showHome()
}
sendSigninResponseToServer()
возвращает true, указывая, что (фиктивный) сервер подтвердил открытый ключ для будущего использования.- После входа в систему вы перенаправляете пользователя на главный экран.
Запустите приложение и перейдите к входу > Войдите в систему, используя ключи/сохраненный пароль, и попробуйте войти, используя сохраненные учетные данные.
Попробуйте это
Вы реализовали создание ключей доступа, сохранение пароля в диспетчере учетных данных и аутентификацию с помощью ключей доступа или сохраненного пароля с помощью API диспетчера учетных данных в своем приложении Android.
6. Поздравляем!
Вы завершили эту кодовую работу! Если вы хотите проверить окончательное решение, которое доступно по адресу https://github.com/android/identity-samples/tree/main/CredentialManager.
Если у вас есть вопросы, задайте их на StackOverflow с помощью тега passkey
.