Аутентификация устанавливает личность пользователя и обычно называется регистрацией или входом в систему. Авторизация — это процесс предоставления или отказа в доступе к данным или ресурсам. Например, ваше приложение запрашивает согласие пользователя на доступ к его Google Диску.
Вызовы аутентификации и авторизации должны представлять собой два отдельных и различных потока в зависимости от потребностей приложения.
Если в вашем приложении есть функции, которые могут использовать данные API Google, но не являются обязательными для основных функций, вам следует разработать приложение так, чтобы оно могло корректно обрабатывать ситуации, когда данные API недоступны. Например, вы можете скрыть список недавно сохранённых файлов, если пользователь не предоставил доступ к Диску.
Запрашивать доступ к областям, необходимым для доступа к API Google, следует только тогда, когда пользователь выполняет действие, требующее доступа к определённому API. Например, запрашивать разрешение на доступ к Диску пользователя следует каждый раз, когда пользователь нажимает кнопку «Сохранить на Диске».
Разделив авторизацию и аутентификацию, вы сможете избежать перегрузки новых пользователей и не запутать их в том, почему у них запрашиваются определенные разрешения.
Для аутентификации мы рекомендуем использовать API Credential Manager . Для авторизации действий, требующих доступа к пользовательским данным, хранящимся в Google, мы рекомендуем использовать AuthorizationClient .
Настройте свой проект
- Откройте свой проект в или создайте проект, если у вас его еще нет.
- На , убедитесь, что вся информация является полной и точной.
- Убедитесь, что вашему приложению назначены правильное название, логотип и домашняя страница. Эти значения будут отображаться пользователям на экране согласия «Войти через Google» при регистрации и на экране «Сторонние приложения и сервисы» .
- Убедитесь, что вы указали URL-адреса политики конфиденциальности и условий обслуживания вашего приложения.
- В Создайте идентификатор клиента Android для своего приложения, если у вас его ещё нет. Вам потребуется указать имя пакета приложения и подпись SHA-1.
- В Создайте новый идентификатор клиента «Веб-приложение», если вы ещё этого не сделали. Поля «Авторизованные источники JavaScript» и «Авторизованные URI перенаправления» пока можно игнорировать. Этот идентификатор клиента будет использоваться для идентификации вашего внутреннего сервера при взаимодействии со службами аутентификации Google.
Объявить зависимости
В файле build.gradle вашего модуля объявите зависимости, используя последнюю версию библиотеки Google Identity Services.
dependencies {
// ... other dependencies
implementation "com.google.android.gms:play-services-auth:21.4.0"
}
Запрос разрешений, необходимых для действий пользователя
Каждый раз, когда пользователь выполняет действие, требующее дополнительных прав, вызывайте метод AuthorizationClient.authorize()
. Например, если пользователь выполняет действие, требующее доступа к хранилищу приложения «Диск», выполните следующие действия:
Котлин
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.launchIntentSenderRequest.Builder(pendingIntent!!.intentSender).build()
} else {
// Access was previously granted, continue with user action
saveToDriveAppFolder(authorizationResult);
}
}
.addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }
Ява
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
}
}
}
Ява
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) }
Ява
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) }
Ява
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 диспетчера учётных данных, прежде чем запрашивать авторизацию. Ответ диспетчера учётных данных включает информацию о пользователе, например адрес электронной почты, а также устанавливает выбранную учётную запись в качестве учётной записи по умолчанию для приложения; при необходимости вы можете отслеживать эту учётную запись в своём приложении. Последующий запрос на авторизацию использует учётную запись по умолчанию, и этап выбора учётной записи в процессе авторизации пропускается. Чтобы использовать другую учётную запись для авторизации, см. раздел Авторизация с учётной записи, не являющейся учётной записью по умолчанию .
В запросе авторизации, помимо необходимых областей действия (например,
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.launchIntentSenderRequest.Builder(
pendingIntent!!.intentSender
).build()
}
.addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }
Ява
// 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
}
}
}
Ява
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
}
});
}