Аутентификация устанавливает, кем является пользователь, и обычно называется регистрацией или входом пользователя. Авторизация – это процесс предоставления или отклонения доступа к данным или ресурсам. Например, приложение запрашивает у пользователя разрешение на доступ к его Google Диску.
Вызовы аутентификации и авторизации должны быть двумя отдельными процессами, основанными на потребностях приложения.
Если в вашем приложении есть функции, которые могут использовать данные API Google, но не являются основными функциями вашего приложения, разработайте приложение так, чтобы оно могло корректно обрабатывать случаи, когда данные API недоступны. Например, вы можете скрыть список недавно сохраненных файлов, если пользователь не предоставил доступ к Диску.
Запрашивайте доступ к областям, необходимым для работы с API Google, только когда пользователь выполняет действие, требующее доступа к определенному API. Например, запрашивайте разрешение на доступ к Диску пользователя, когда он нажимает кнопку Сохранить на Диске.
Разделяя авторизацию и аутентификацию, вы можете избежать перегрузки новых пользователей или запутать пользователей в том, почему у них запрашивают определенные разрешения.
Для аутентификации рекомендуем использовать Credential Manager API. Для авторизации действий, которым требуется доступ к пользовательским данным, хранящимся в Google, рекомендуется использовать AuthorizationClient.
Как настроить проект в консоли Google Cloud
- Откройте проект в Cloud Console или создайте его, если у вас его ещё нет.
- На странице Брендинг убедитесь, что вся информация указана полностью и правильно.
- Убедитесь, что у приложения правильно указаны название, логотип и главная страница. Эти значения будут показываться пользователям в окне запроса доступа при входе с аккаунтом Google и на странице сторонних приложений и сервисов.
- Убедитесь, что вы указали URL политики конфиденциальности и условий использования приложения.
- На странице Клиенты создайте идентификатор клиента Android для своего приложения, если вы этого ещё не сделали. Вам нужно будет указать название пакета приложения и подпись SHA-1.
- Перейдите на страницу Клиенты.
- Нажмите Создать клиент.
- Выберите тип приложения Android.
- Введите название клиента OAuth. Это название будет показываться на странице клиентов проекта.
- Укажите название пакета приложения для Android. Это значение задается в атрибуте
packageэлемента<manifest>в файлеAndroidManifest.xml. - Введите цифровой отпечаток сертификата подписи SHA-1 для распространения приложения.
- Если в вашем приложении используется функция подписания приложений в Google Play, скопируйте цифровой отпечаток SHA-1 со страницы подписания приложений в Play Console.
- Если вы управляете собственным хранилищем ключей и ключами подписи, используйте утилиту keytool, входящую в состав Java, чтобы вывести информацию о сертификате в человекочитаемом формате. Скопируйте значение
SHA-1в разделеCertificate fingerprintsвыходных данных keytool. Подробную информацию можно найти в разделе Аутентификация клиента документации по API Google для Android. - Подтвердите право собственности на приложение для Android (необязательно).
- На странице Клиенты создайте идентификатор клиента для веб-приложения, если вы ещё этого не сделали. Поля "Разрешенные источники JavaScript" и "Разрешенные URI перенаправления" пока можно не заполнять. Этот идентификатор клиента будет использоваться для идентификации вашего бэкенд-сервера при его взаимодействии с сервисами аутентификации Google.
- Перейдите на страницу Клиенты.
- Нажмите Создать клиент.
- Выберите тип Веб-приложение.
Подтвердите право собственности на приложение
Вы можете подтвердить право собственности на приложение, чтобы снизить риск его имитации.
Чтобы пройти проверку, вы можете использовать аккаунт разработчика Google Play, если он у вас есть и ваше приложение зарегистрировано в Google Play Console. Для успешной проверки должны быть выполнены следующие требования:
- У вас должно быть зарегистрированное приложение в Google Play Console с тем же названием пакета и цифровым отпечатком сертификата для подписи SHA-1, что и у клиента OAuth для Android, для которого вы проходите проверку.
- У вас должны быть права администратора для приложения в Google Play Console. Подробнее об управлении доступом в Google Play Console…
В разделе Подтверждение права собственности на приложение клиента Android нажмите кнопку Подтвердить право собственности, чтобы завершить процесс проверки.
Если проверка пройдет успешно, вы увидите уведомление об этом. В противном случае появится сообщение об ошибке.
Чтобы устранить проблему, попробуйте сделать следующее:
- Убедитесь, что приложение, которое вы проверяете, зарегистрировано в Google Play Console.
- Убедитесь, что у вас есть разрешение Администратор для приложения в Google Play Console.
Объявление зависимостей
В файле build.gradle модуля объявите зависимости, используя последнюю версию библиотеки сервисов идентификации Google.
dependencies {
// ... other dependencies
implementation "com.google.android.gms:play-services-auth:22.0.0"
}
Запрос разрешений, необходимых для действий пользователя
Когда пользователь выполняет действие, требующее дополнительной области действия, вызовите AuthorizationClient.authorize(). Например, если пользователь выполняет действие, для которого требуется доступ к хранилищу приложения на Диске, сделайте следующее:
Kotlin
val requestedScopes: List<Scope> = listOf(DriveScopes.DRIVE_FILE)
val authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.build()
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequestBuilder.build())
.addOnSuccessListener { authorizationResult ->
if (authorizationResult.hasResolution()) {
val pendingIntent = authorizationResult.pendingIntent
// Access needs to be granted by the user
startAuthorizationIntent.launch(IntentSenderRequest.Builder(pendingIntent!!.intentSender).build())
} else {
// Access was previously granted, continue with user action
saveToDriveAppFolder(authorizationResult);
}
}
.addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }
Java
List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.build();
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequest)
.addOnSuccessListener(authorizationResult -> {
if (authorizationResult.hasResolution()) {
// Access needs to be granted by the user
startAuthorizationIntent.launch(
new IntentSenderRequest.Builder(
authorizationResult.getPendingIntent().getIntentSender()
).build()
);
} else {
// Access was previously granted, continue with user action
saveToDriveAppFolder(authorizationResult);
}
})
.addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));
При определении ActivityResultLauncher обработайте ответ, как показано в следующем фрагменте кода. Предполагается, что это делается во фрагменте. Код проверяет, что необходимые разрешения были успешно предоставлены, а затем выполняет действие пользователя.
Kotlin
private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
// ...
startAuthorizationIntent =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
try {
// extract the result
val authorizationResult = Identity.getAuthorizationClient(requireContext())
.getAuthorizationResultFromIntent(activityResult.data)
// continue with user action
saveToDriveAppFolder(authorizationResult);
} catch (e: ApiException) {
// log exception
}
}
}
Java
private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
startAuthorizationIntent =
registerForActivityResult(
new ActivityResultContracts.StartIntentSenderForResult(),
activityResult -> {
try {
// extract the result
AuthorizationResult authorizationResult =
Identity.getAuthorizationClient(requireActivity())
.getAuthorizationResultFromIntent(activityResult.getData());
// continue with user action
saveToDriveAppFolder(authorizationResult);
} catch (ApiException e) {
// log exception
}
});
}
Если вы обращаетесь к API Google на стороне сервера, вызовите метод getServerAuthCode() из AuthorizationResult, чтобы получить код авторизации, который вы отправляете на свой бэкенд для обмена на токен доступа и токен обновления. Подробнее о том, как сохранить доступ к данным пользователя…
Как отозвать разрешения на доступ к пользовательским данным или ресурсам
Чтобы отозвать ранее предоставленный доступ, вызовите AuthorizationClient.revokeAccess(). Например, если пользователь удаляет свой аккаунт из вашего приложения, которому ранее был предоставлен доступ к DriveScopes.DRIVE_FILE, используйте следующий код, чтобы отозвать доступ:
Kotlin
val requestedScopes: MutableList<Scope> = mutableListOf(DriveScopes.DRIVE_FILE)
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
.setAccount(account)
.setScopes(requestedScopes)
.build()
Identity.getAuthorizationClient(activity)
.revokeAccess(revokeAccessRequest)
.addOnSuccessListener { Log.i(TAG, "Successfully revoked access") }
.addOnFailureListener { e -> Log.e(TAG, "Failed to revoke access", e) }
Java
List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
.setAccount(account)
.setScopes(requestedScopes)
.build();
Identity.getAuthorizationClient(activity)
.revokeAccess(revokeAccessRequest)
.addOnSuccessListener(unused -> Log.i(TAG, "Successfully revoked access"))
.addOnFailureListener(e -> Log.e(TAG, "Failed to revoke access", e));
Очистите кеш токенов
Токены доступа OAuth кешируются локально после получения с сервера, что ускоряет доступ и сокращает количество сетевых вызовов. Эти токены автоматически удаляются из кеша по истечении срока действия, но могут стать недействительными и по другим причинам.
Если при использовании токена вы получаете ошибку IllegalStateException, очистите локальный кеш, чтобы следующий запрос авторизации для токена доступа был отправлен на сервер OAuth. Следующий фрагмент кода удаляет invalidAccessToken из локального кеша:
Kotlin
Identity.getAuthorizationClient(activity)
.clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
.addOnSuccessListener { Log.i(TAG, "Successfully removed the token from the cache") }
.addOnFailureListener{ e -> Log.e(TAG, "Failed to clear token", e) }
Java
Identity.getAuthorizationClient(activity)
.clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
.addOnSuccessListener(unused -> Log.i(TAG, "Successfully removed the token from the cache"))
.addOnFailureListener(e -> Log.e(TAG, "Failed to clear the token cache", e));
Получение информации о пользователе во время авторизации
Ответ на запрос авторизации не содержит никакой информации об использованном аккаунте пользователя, а только токен для запрошенных областей действия. Например, ответ для получения токена доступа к Google Диску пользователя не раскрывает идентификационные данные аккаунта, выбранного пользователем, хотя его можно использовать для доступа к файлам на Диске пользователя. Чтобы получить информацию, например имя или адрес электронной почты пользователя, вы можете:
Прежде чем запрашивать авторизацию, выполните вход пользователя в аккаунт Google с помощью Credential Manager API. Ответ аутентификации от Менеджера учетных данных включает информацию о пользователе, например адрес электронной почты, и устанавливает выбранный аккаунт в качестве аккаунта по умолчанию для приложения. При необходимости вы можете отслеживать этот аккаунт в своем приложении. Последующий запрос авторизации использует аккаунт по умолчанию и пропускает шаг выбора аккаунта в процессе авторизации. Если вы хотите использовать для авторизации другой аккаунт, ознакомьтесь с разделом Авторизация из аккаунта, не используемого по умолчанию.
В запросе на авторизацию, помимо нужных вам областей действия (например,
Drive scope), запросите области действияuserinfo,profileиopenid. После получения токена доступа запросите информацию о пользователе, отправив HTTP-запросGETк конечной точке OAuth userinfo (https://www.googleapis.com/oauth2/v3/userinfo). Для этого используйте любую HTTP-библиотеку и добавьте в заголовок полученный токен доступа. Это эквивалентно следующей командеcurl:curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"В ответ вы получите объект
UserInfoв формате JSON, ограниченный запрошенными областями действия.
Авторизация из аккаунта, не являющегося аккаунтом по умолчанию
Если вы используете Менеджер учетных данных для аутентификации и запускаете AuthorizationClient.authorize(), аккаунтом по умолчанию для вашего приложения будет выбранный пользователем. Это означает, что все последующие вызовы для авторизации будут использовать этот аккаунт по умолчанию. Чтобы принудительно показать окно выбора аккаунта, выполните выход пользователя из приложения с помощью API clearCredentialState() из Менеджера учетных данных.
Сохранять постоянный доступ к данным пользователя
Если вашему приложению нужен доступ к данным пользователя, вызовите метод AuthorizationClient.authorize() один раз. В последующих сеансах, пока пользователь не отзовет предоставленные разрешения, вызывайте тот же метод, чтобы получить токен доступа и выполнить необходимые действия без участия пользователя. Если же вам нужен доступ к данным пользователя в офлайн-режиме с бэкенд-сервера, запросите токен обновления.
Токены доступа намеренно создаются с коротким сроком действия – один час. Если токен доступа будет перехвачен или скомпрометирован, ограниченный срок его действия минимизирует потенциальный ущерб. После этого токен становится недействительным, и любые попытки его использования будут отклонены сервером ресурсов. Поскольку токены доступа действуют недолго, серверы используют токены обновления, чтобы поддерживать постоянный доступ к данным пользователя. Токены обновления – это токены с длительным сроком действия, которые используются клиентом для запроса у сервера авторизации токена доступа с коротким сроком действия, когда срок действия старого токена доступа истекает, без какого-либо взаимодействия с пользователем.
Чтобы получить токен обновления, сначала нужно получить код авторизации на этапе авторизации в приложении, запросив офлайн-доступ, а затем обменять код авторизации на токен обновления на сервере. Долгосрочные токены обновления необходимо хранить на сервере в безопасном месте, поскольку их можно использовать многократно для получения новых токенов доступа. Поэтому из соображений безопасности настоятельно не рекомендуется хранить токены обновления на устройстве. Их следует хранить на бэкенд-серверах приложения, где происходит обмен на токен доступа.
После того как код авторизации будет отправлен на бэкенд-сервер вашего приложения, вы можете обменять его на токен доступа с коротким сроком действия на сервере и токен обновления с длительным сроком действия, следуя инструкциям в руководстве по авторизации аккаунта. Этот обмен должен происходить только в серверной части вашего приложения.
Kotlin
// Ask for offline access during the first authorization request
val authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.requestOfflineAccess(serverClientId)
.build()
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequest)
.addOnSuccessListener { authorizationResult ->
startAuthorizationIntent.launch(IntentSenderRequest.Builder(
pendingIntent!!.intentSender
).build())
}
.addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }
Java
// Ask for offline access during the first authorization request
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.requestOfflineAccess(serverClientId)
.build();
Identity.getAuthorizationClient(getContext())
.authorize(authorizationRequest)
.addOnSuccessListener(authorizationResult -> {
startAuthorizationIntent.launch(
new IntentSenderRequest.Builder(
authorizationResult.getPendingIntent().getIntentSender()
).build()
);
})
.addOnFailureListener(e -> Log.e(TAG, "Failed to authorize"));
В приведенном ниже фрагменте кода предполагается, что авторизация запускается из фрагмента.
Kotlin
private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
// ...
startAuthorizationIntent =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
try {
val authorizationResult = Identity.getAuthorizationClient(requireContext())
.getAuthorizationResultFromIntent(activityResult.data)
// short-lived access token
accessToken = authorizationResult.accessToken
// store the authorization code used for getting a refresh token safely to your app's backend server
val authCode: String = authorizationResult.serverAuthCode
storeAuthCodeSafely(authCode)
} catch (e: ApiException) {
// log exception
}
}
}
Java
private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
startAuthorizationIntent =
registerForActivityResult(
new ActivityResultContracts.StartIntentSenderForResult(),
activityResult -> {
try {
AuthorizationResult authorizationResult =
Identity.getAuthorizationClient(requireActivity())
.getAuthorizationResultFromIntent(activityResult.getData());
// short-lived access token
accessToken = authorizationResult.getAccessToken();
// store the authorization code used for getting a refresh token safely to your app's backend server
String authCode = authorizationResult.getServerAuthCode()
storeAuthCodeSafely(authCode);
} catch (ApiException e) {
// log exception
}
});
}