Разрешить доступ к пользовательским данным Google

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

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

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

Доступ к необходимым API Google следует запрашивать только тогда, когда пользователь выполняет действие, требующее доступа к конкретному API. Например, разрешение на доступ к Google Диску следует запрашивать всякий раз, когда пользователь нажимает кнопку « Сохранить в Диск» .

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

Для аутентификации мы рекомендуем использовать API Credential Manager . Для авторизации действий, требующих доступа к пользовательским данным, хранящимся в Google, мы рекомендуем использовать AuthorizationClient .

Настройте свой Google Cloud Console проект

  1. Откройте свой проект вCloud Console или создайте проект, если у вас его еще нет.
  2. НаBranding page Убедитесь, что вся информация является полной и точной.
    1. Убедитесь, что вашему приложению присвоены правильное название, логотип и домашняя страница. Эти значения будут отображаться пользователям на экране согласия Google при регистрации и на экране сторонних приложений и сервисов .
    2. Убедитесь, что вы указали URL-адреса политики конфиденциальности и условий использования вашего приложения.
  3. ВClients page Если у вас его еще нет, создайте идентификатор клиента Android для вашего приложения. Вам потребуется указать имя пакета вашего приложения и подпись SHA-1.
    1. Перейдите вClients page .
    2. Нажмите «Создать клиента» .
    3. Выберите тип приложения для Android .
    4. Введите имя для клиента OAuth. Это имя будет отображаться в вашем проекте.Clients page идентифицировать клиента.
    5. Введите имя пакета вашего Android-приложения. Это значение определяется в атрибуте package элемента ` <manifest> ` в файле ` AndroidManifest.xml .
    6. Введите отпечаток сертификата подписи SHA-1 дистрибутива приложения.
    7. Если ваше приложение использует электронную подпись Google Play , скопируйте отпечаток SHA-1 со страницы электронной подписи приложения в Play Console.
    8. Если вы управляете собственным хранилищем ключей и ключами подписи, используйте утилиту keytool, входящую в состав Java, для вывода информации о сертификате в удобочитаемом формате. Скопируйте значение SHA-1 из раздела Certificate fingerprints в выводе keytool . Дополнительную информацию см. в разделе «Аутентификация клиента» в документации Google API для Android.
    9. (Необязательно) Подтвердите права собственности на ваше Android-приложение.
  4. ВClients page Если вы еще этого не сделали, создайте новый идентификатор клиента для «Веб-приложения». Пока можете игнорировать поля «Авторизованные источники JavaScript» и «Авторизованные URI перенаправления». Этот идентификатор клиента будет использоваться для идентификации вашего бэкэнд-сервера при взаимодействии со службами аутентификации Google.
    1. Перейдите вClients page .
    2. Нажмите «Создать клиента» .
    3. Выберите тип веб-приложения .

Подтвердите права собственности на приложение.

Вы можете подтвердить право собственности на ваше приложение, чтобы снизить риск подделки приложения.

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

  • У вас должно быть зарегистрировано приложение в Google Play Console с тем же именем пакета и отпечатком сертификата подписи SHA-1, что и у клиента Android OAuth, для которого вы выполняете проверку.
  • Для работы приложения в консоли Google Play у вас должны быть права администратора . Подробнее об управлении доступом можно узнать в консоли Google Play.

В разделе «Проверка прав собственности на приложение» в Android-клиенте нажмите кнопку «Проверить права собственности» , чтобы завершить процесс проверки.

Если проверка пройдена успешно, отобразится уведомление, подтверждающее успешное завершение процесса проверки. В противном случае будет показано сообщение об ошибке.

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

  • Убедитесь, что проверяемое приложение зарегистрировано в Google Play Console.
  • Убедитесь, что у вас есть права администратора для приложения в консоли Google Play.

Объявление зависимостей

В файле build.gradle вашего модуля укажите зависимости, использующие последнюю версию библиотеки Google Identity Services.

dependencies {
  // ... other dependencies

  implementation "com.google.android.gms:play-services-auth:21.5.0"
}

Запросить разрешения, необходимые для действий пользователя.

Всякий раз, когда пользователь выполняет действие, требующее дополнительных полномочий, вызовите метод AuthorizationClient.authorize() . Например, если пользователь выполняет действие, требующее доступа к хранилищу приложения Google Диск, выполните следующие действия:

Котлин

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

Котлин

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 (ApiException e) {
                // 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 , используйте следующий код для отзыва доступа:

Котлин

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 из локального кэша:

Котлин

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, используя API Credential Manager . Ответ на запрос аутентификации от Credential Manager включает информацию о пользователе, такую ​​как адрес электронной почты, а также устанавливает в качестве учетной записи по умолчанию выбранную учетную запись; при необходимости вы можете отслеживать эту учетную запись в своем приложении. Последующий запрос на авторизацию использует эту учетную запись в качестве учетной записи по умолчанию и пропускает этап выбора учетной записи в процессе авторизации. Чтобы использовать другую учетную запись для авторизации, см. раздел «Авторизация из учетной записи, отличной от учетной записи по умолчанию» .

  • В запросе на авторизацию, помимо необходимых вам областей действия (например, 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.

Авторизация с учетной записи, не являющейся учетной записью по умолчанию.

Если вы используете Credential Manager для аутентификации и запускаете AuthorizationClient.authorize() , то в качестве учетной записи по умолчанию для вашего приложения устанавливается та, которую выбрал пользователь. Это означает, что все последующие вызовы авторизации будут использовать эту учетную запись по умолчанию. Чтобы принудительно отобразить окно выбора учетной записи, выйдите из приложения, используя API clearCredentialState() из Credential Manager.

Обеспечивать постоянный доступ к данным пользователя.

Если вам необходимо получить доступ к данным пользователя из вашего приложения, вызовите AuthorizationClient.authorize() один раз; в последующих сессиях, и пока пользователь не отзовет предоставленные разрешения, вызывайте тот же метод для получения токена доступа, чтобы достичь своих целей без какого-либо взаимодействия с пользователем. Если же вам необходимо получить доступ к данным пользователя в автономном режиме, с вашего бэкэнд-сервера, то вам потребуется запросить другой тип токена, называемый «токеном обновления».

Токены доступа специально разработаны таким образом, чтобы иметь короткий срок действия — один час. Если токен доступа перехвачен или скомпрометирован, ограниченный период его действия сводит к минимуму потенциальное неправомерное использование. После истечения срока действия токен становится недействительным, и любые попытки его использования будут отклонены сервером ресурсов. Поскольку токены доступа имеют короткий срок действия, серверы используют токены обновления для поддержания постоянного доступа к данным пользователя. Токены обновления — это токены с длительным сроком действия, которые используются клиентом для запроса кратковременного токена доступа у сервера авторизации, когда срок действия старого токена доступа истекает, без какого-либо взаимодействия с пользователем.

Для получения токена обновления вам сначала потребуется получить код авторизации (или код авторизации) на этапе авторизации в вашем приложении, запросив «автономный доступ», а затем обменять этот код авторизации на токен обновления на вашем сервере. Крайне важно надежно хранить долгосрочные токены обновления на вашем сервере, поскольку они могут многократно использоваться для получения новых токенов доступа. Поэтому настоятельно не рекомендуется хранить токены обновления на устройстве из-за соображений безопасности. Вместо этого их следует хранить на бэкэнд-серверах приложения, где происходит обмен на токен доступа.

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

Котлин

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

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

Котлин

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