Мобильная социализация. Полный гайд по использованию OAuth-авторизации для Android — Хакер

Progressdialog

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

mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage(getString(R.string.loading));
mProgressDialog.setIndeterminate(true);
mProgressDialog.show();

Его будет логично использовать при старте Activity, если приложению придется запрашивать у AS токен доступа. Когда вычисления закончатся, убрать этот элемент можно, вызвав метод hide.

if (opr.isDone()) {
  ...
} else {
  showProgressDialog();
  ...
}
mProgressDialog.hide();

Signinbutton

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

Silientsignin

Чтобы пользователю не приходилось снова и снова вводить свои учетные данные, в GSI доступна возможность тихого входа. Для этого нам понадобится метод onStart. При тихом входе проверяется состояние токена доступа к PR. Если пользователь недавно из нашего приложения входил в свой аккаунт, то все хорошо и приложение сразу же получит все нужные данные. В противном случае начнется обмен данными с AS и приложение с небольшой задержкой получит новый токен.

OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
  GoogleSignInResult result = opr.get();
  handleSignInResult(result);
} else {
  opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
    @Override
    public void onResult(GoogleSignInResult googleSignInResult) {
      handleSignInResult(googleSignInResult);
      ...
    }
  }
}
Рис. 3. Пример работы GSI
Рис. 3. Пример работы GSI

Аутентификация oauth2 в приложении посредством google sign-in. непрерывный доступ к api google

«С 20 апреля 2022 года отправка запросов на авторизацию из встроенных браузеров будет блокироваться».

Такое сообщение с 1 марта можно увидеть в некоторых приложениях, где необходима авторизация. Об этом Google написали в своём блоге еще в августе 2022, и это значит, что скоро во многих приложениях придется переписывать реализацию регистрации. Приятного мало, однако выход есть – использовать рекомендуемый способ авторизации Google Sign-in.

Об этом способе и будет идти речь в уроке, а также как получить токены, необходимые для работы с API Google.

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

Понадобилось мне для приложения получить чат с прямой трансляции YouTube. И тогда я узнала, что для отправки запросов на получение трансляции (и только потом чата) необходимо провести OAuth2 аутентификацию пользователя. Я начала искать. Информации по такой теме очень мало, она разрознена, не подходила для моего случая, и конечно же всё было на английском языке. В основном информация была для работы с наиболее популярными API: Drive, Cloud, Google Plus. В официальной документации API YouTube есть готовый код, бери да пользуйся, однако для Android он не подходит. Потратив немалое количество времени, методом проб и ошибок я пришла к рабочему решению. Первое, что мне захотелось сделать после, это собрать информацию «в кучу» и разложить по полочкам, что и сподвигло на написание этого урока.

Изначально моё решение начиналось с того, что перед пользователем открывался WebView для авторизации (ввод email, пароля). Далее запрашивалось разрешение на использование данных, и только после разрешения в ответе приходил код аутентификации (AuthCode), подробнее что с ним делать будет далее. Url, который открывался в WebView был следующий:

https://accounts.google.com/o/oauth2/auth?
client_id=60*********5ad3np.apps.googleusercontent.com
&redirect_uri=urn:ietf:wg:oauth:2.0:oob
&access_type=offline&response_type=code
&scope=https://www.googleapis.com/auth/youtube.readonly

Это ни что иное, как post запрос, в ответ на который приходила страница, содержащая authCode, причем код был в заголовке страницы. Всё, как по рекомендации к API, а действия для сокрытия этого кода от пользователя оставили на разработчика.

Всё работало хорошо, приложение опубликовано, но в один прекрасный день я увидела следующее:

Перейдя по ссылке «Подробнее» попадаем в блог, где сказано, что во имя безопасности, аутентификация через WebView работать не будет с 20 апреля. Ну вот, думаю я, только сделала и придется переделывать через Sign-In. Причем первоначально я пыталась сделать реализацию именно через этот сервис. Однако с уже имеющимися знаниями «что и зачем» получилось довольно быстро. И так, начнем.

Похожее:  Интернет-банк - Хоум Кредит Банк

1. Получение учетных данных

В Диспетчере API создаем новый проект (или выбираем существующий):

Для авторизации понадобится файл конфигурации, который можно получить

в мастере

:

Заполняем поля название приложения и пакет. Далее выбираем какой сервис подключаем (Google Sign-In), здесь нужно ввести SHA1 ключ приложения, получить его просто: в Android Studio находим вкладку Gradle, раскрываем вкладки Tasks-android-signingReport. Щелкаем два раза, и в логах появится информация о ключах. Находим ключ SHA1, копируем.

Жмем кнопку «Generate configuration file», а после «Download google-services.json». Этот файл json сохраняем в папку проекта «app».

Важно! Если вы собираетесь публиковать приложение в Google Play, debug ключ SHA1 нужно будет заменить на release ключ, соответственно и заменить файл конфигурации.

Заходим в Диспетчер API, видим, что сгенерировались ключи и идентификаторы клиентов OAuth. Нам понадобятся только данные Web client (идентификатор клиента и секрет клиента).

Мобильная социализация. Полный гайд по использованию OAuth-авторизации для Android — Хакер
Во вкладке «Окно запроса доступа OAuth» можно поменять email и название продукта — это то, что будет написано, когда будет запрашиваться разрешение «Приложение **** запрашивает: …»

2. Настройка Sign-In клиента

Чтобы получить доступ к Google Api Client, в файл gradle app нужно добавить в зависимости:

compile 'com.google.android.gms:play-services-auth:10.2.0'

И плагин (в конец файла):

apply plugin: 'com.google.gms.google-services'

В файл gradle project в зависимости:

classpath 'com.google.gms:google-services:3.0.0'

Настраиваем опции:

GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
				.requestServerAuthCode(getString(R.string.server_client_id))
				.requestEmail()
				.requestScopes(new Scope("https://www.googleapis.com/auth/youtube.readonly"))
				.build();

Здесь наибольший интерес вызывают строки:

requestServerAuthCode(getString(R.string.server_client_id)) – запрашиваем authCode, передавая параметр идентификатор клиента (весь полностью), который получили выше.

requestScopes(new Scope("***")) – запрашиваем необходимую для используемого API область/области доступа. Есть некоторые уже определенные области в Scopes, но, если нужной там не нашлось, можно задать свою, как в моём случае. Для пользователя будет отображаться как доступ «к чему» хочет получить приложение.

Настраиваем клиент:

GoogleApiClient mApiClient = new GoogleApiClient.Builder(this)
				.enableAutoManage(this, this)
				.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
				.build();

Тут всё по стандарту из документации:

enableAutoManage(this, this) – в параметры передается активити и слушатель соединения (реализуем интерфейс GoogleApiClient.OnConnectionFailedListener).

addApi(Auth.GOOGLE_SIGN_IN_API, gso) – указываем, что используем Sign In api и ранее созданный объект опций.

Теперь для запуска авторизации нужна кнопка (либо любой другой элемент). Можно использовать стандартную кнопку Sign In Button. Разметка xml:

<com.google.android.gms.common.SignInButton
			android:id="@ id/activity_button_sign_in"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content" />

Выглядит она так:

Мобильная социализация. Полный гайд по использованию OAuth-авторизации для Android — Хакер

В активити кнопка определяется как и все другие view, на нее повешаем слушатель и по клику выполним метод:

@Override
public void onClick(View view) {
	switch (view.getId()) {
		case R.id.activity_button_sign_in:
			signIn();
			break;
	}
}

Код вызываемого метода представляет собой создание интента и вызов активити для авторизации:

public void signIn() {
		Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mApiClient);
		startActivityForResult(signInIntent, RC_AUTH_CODE);
}

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

При нажатии на копку, будет предложено выбрать аккаунт для входа, либо добавить новый. После выбора, приложение запросит разрешение:

3. Получение Auth code

После того, как пользователь даст разрешение, в onActivityResult получаем данные его аккаунта:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	if (requestCode == RC_AUTH_CODE) {
		GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
		if (result.isSuccess()) {
			GoogleSignInAccount acct = result.getSignInAccount();
			String authCode = acct.getServerAuthCode();
			getAccessToken(authCode);
		} 
	}
}

В результате получаем auth code как обычную строку, выглядит он примерно так:

4/iHhVmqtxccXh0Qs*********oo5XG8OjaNsWu_kEKyw

Так же из аккаунта можно получить email пользователя, username и аватарку:

acct.getEmail()
acct.getDisplayName()
acct.getPhotoUrl()

Эти данные могут понадобиться, например, чтобы вставить их в header NavigationView.

4. Получение Access Token и Refresh Token

Получили auth code, теперь его нужно поменять на необходимые для запросов к API токены. Для этого формируем запрос по адресу https://www.googleapis.com/oauth2/v4/token. Например я сделаю это с помощью OkHttp.

public void getAccessToken(String authCode) {
	OkHttpClient client = new OkHttpClient();
	RequestBody requestBody = new FormEncodingBuilder()
			.add("grant_type", "authorization_code")
			.add("client_id", getString(R.string.server_client_id))
			.add("client_secret", getString(R.string.client_secret))
			.add("code", authCode)
			.build();
	final Request request = new Request.Builder()
			.url("https://www.googleapis.com/oauth2/v4/token")
			.header("Content-Type", "application/x-www-form-urlencoded")
			.post(requestBody)
			.build();
	client.newCall(request).enqueue(new Callback() {
		@Override
		public void onFailure(Request request, IOException e) {}
		@Override
		public void onResponse(Response response) throws IOException {
			try {
				JSONObject jsonObject = new JSONObject(response.body().string());
				mAccessToken = jsonObject.get("access_token").toString();
				mTokenType = jsonObject.get("token_type").toString();
				mRefreshToken = jsonObject.get("refresh_token").toString();
			} catch (JSONException e) {
				e.printStackTrace();
			}
		}
	});
}

Рассмотрим подробнее параметры. В Request.Builder() Передаем url по которому получаем токены:

url("https://www.googleapis.com/oauth2/v4/token")

В header указываем Content-Type:

header("Content-Type", "application/x-www-form-urlencoded")

Указываем, что это метод POST, в него передаем body:

post(requestBody)

Сформированный requestBody обязательно должен содержать параметры:

Похожее:  Скачать Госуслуги на телефон и планшет

"grant_type", "authorization_code" – указываем, что передавать будем auth code
"client_id", getString(R.string.server_client_id) – параметр является client id, полученный в Диспетчере API
"client_secret", getString(R.string.client_secret) — секрет клиента, полученный в Диспетчере API
"code", authCode – собственно полученный код.

Запрос асинхронный, в ответе получаем обычный json со всеми нужными для работы данными:

{ "access_token":"ya29.GlsfBJNMTfGy…",
"token_type":"Bearer",
"expires_in":3600,
"refresh_token":"1/72OqA7zYuyY__XhGij5oA2nEb7…",
"id_token":"eyJhbGciOiJSUzI1NiIsImtpZ…" }

"access_token"

– токен доступа, ради которого всё проводилось

"expires_in"

– время жизни access токена, по умолчанию токен живет 1 час, а в сутки можно получать по запросу 25 токенов, не более.

"token_type"

– тип токена, его тоже необходимо запомнить, он также вставляется в запрос к api в дальнейшем.

"refresh_token"

– токен для обновления access токена, когда пройдет час жизни. Refresh токен неизменен. Часто на форумах видела проблему, с которой сталкивалась и сама: в запросе не приходил этот токен. Ошибки заключаются в неправильном получении учетных данных, либо неправильные запросы. Если авторизация проводилась через WebView, и в url не указывался такой важный параметр как access_type=offline, то refresh токен попросту не приходил.

5. Обновление Access токена

Час прошел, access токен больше не активен, необходим новый. После этого посыпятся ошибки 401 или 403, сервер скажет, что пользователь не авторизован или не имеет доступа. Запрашивать новое разрешение не годится, если нам нужна непрерывная сессия, например как у меня, нужно непрерывно получать сообщения из чата в течении трансляции, а это несколько часов. Что делать? Посылать запрос на получение нового токена.

Запрос в основном такой же как в пункте 4, за исключением некоторых параметров:

private void getNewAccessToken() {
	OkHttpClient client = new OkHttpClient();
	RequestBody requestBody = new FormEncodingBuilder()
				.add("refresh_token", mRefreshToken)
				.add("client_id", getString(R.string.server_client_id))
			        .add("client_secret", getString(R.string.client_secret))
				.add("grant_type", "refresh_token")
				.build();
	final Request request = new Request.Builder()
				.url("https://www.googleapis.com/oauth2/v4/token")
				.header("Content-Type", "application/x-www-form-urlencoded")
				.post(requestBody)
				.build();
	client.newCall(request).enqueue(new Callback() {
		@Override
		public void onFailure(Request request, IOException e) {}
		@Override
		public void onResponse(Response response) throws IOException {
			try {
				JSONObject jsonObject = new JSONObject(response.body().string());
				mAccessToken = jsonObject.get("access_token").toString();
			} catch (JSONException e) {
				e.printStackTrace();
			}
		}
	});
}

Здесь важные параметры:

"grant_type", "refresh_token" – в типе указываем что посылаем refresh токен
"refresh_token", mRefreshToken – и сам токен

Ответом будет json, содержащий новый access токен, с которым снова можно обращаться к API:

{ "access_token":"ya29.GlsfBM7Y...",
"token_type":"Bearer",
"expires_in":3600,
"id_token":"eyJhbGciOiJ..." }

На этом авторизация и аутентификация пользователя завершена.

Для примера покажу как выглядит запрос к API, а также как я выполняю обновление токена.
Для запроса к API я использую Retrofit2 RxAndroid. Так выглядит запрос на получение чата прямой трансляции:

@GET(GoogleApiUrls.Youtube.CHAT)
Observable<ChatResponse> getChat(@Header("Authorization") String token, @Query("liveChatId") String liveChatId, @Query("part") String part);

Здесь важно заметить, что в header по ключу Authorization должны передаваться тип токена и сам access токен. То есть так:

Authorization Bearer ya29.GlsfBJNMTfGy…

Далее делаю запрос через RxAndroid, и так как в коллбэк onError приходят всевозможные ошибки, то туда же приходит ошибка HttpException с кодом 401 Unauthorized по истечении часа. Здесь же я обрабатываю её, проверяю, если это та самая ошибка, то привожу к соответствующему типу, проверяю действительно ли это код 401 и выполняю метод получения нового токена, затем повторяю запрос.

@Override
public void onError(Throwable e) {
	if (e instanceof HttpException) {
		HttpException exception = (HttpException) e;
		if (exception.code() == 401) {
			getNewAccessToken();
		}
	}
}

Так же для проверки токена существует GET запрос:

https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=ya29.GlsfBJNMTfGy…

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

Опять же, реализацию обновления токена Google оставляет на разработчика.

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

Рекомендую перед началом работы проверять запросы в стороннем приложении/расширении, например Postman, чтобы убедиться в правильности ввода параметров и полученных ответах. Я буду очень рада, если кому-то урок окажется полезным!

Ссылки на используемую документацию:

1.Google Sign-In for Android
2.Youtube API. OAuth 2.0 flows

Библиотека gsi

OAuth работает практически из коробки. К примеру, Google реализовала этот механизм в подключаемой библиотеке. В данном случае этот механизм называется Google Sign-In (GSI), и реализован он в библиотеке Google Play Services. Для ее подключения необходимо изменить оба Gradle-файла, но мы уже не раз пользовались этой библиотекой, поэтому трудностей у тебя возникнуть не должно.

classpath 'com.google.gms:google-services:2.0.0-alpha6'
apply plugin: 'com.google.gms.google-services'
compile 'com.google.android.gms:play-services-auth:9.0.2'
Рис. 2. Google Play Services (с) Google
Рис. 2. Google Play Services (с) Google

Генерация ключа

Как и при работе с любым другим API из библиотеки Google Play Services, необходимо добавить в приложение конфигурационный файл, созданный на сайте Google. Обрати внимание, что в этот раз он будет жестко привязан к цифровой подписи устройства, на котором разрабатывается приложение. Поэтому если потом проект с созданным конфигом собрать на другом компьютере, то GSI работать не станет.

Похожее:  Выбираем приложение для чтения на android-устройствах: FBReader

Интегрируемся

Поскольку GSI будет сам отрисовывать интерфейс аутентификации пользователя, для его реализации целесообразно выделить класс — наследник компонента Activity. Начнем с класса GoogleApiСlient, объект которого должен быть создан раньше всех. Это базовый класс для работы с любыми функциями из Google Play Services. Когда все действия совершаются в Activity, то это подключение удобнее реализовать в методе onCreate.

protected void onCreate(Bundle savedInstanceState) {
mGoogleApiClient = new GoogleApiClient.Builder(this)
  .enableAutoManage(this, this)
  .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
  .build();

Объект собирается с помощью сборщика GoogleApiClient.Builder, затем он самостоятельно подключается к серверу Google (enableAutoManage) и реализует API, в данном случае это GSI (константа GOOGLE_SIGN_IN_API).

Когда пользователь будет вводить пароль к своему аккаунту, Google еще раз спросит, точно ли приложению они нужны. Данные, которые запрашивает приложение, задаются заранее объектом gso — класс GoogleSignInOptions. Если приложению будут нужны email и данные из профиля пользователя, то объект собирается билдером вот с такими параметрами.

GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
  .requestEmail().requestProfile().build();

Ложка дегтя

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

  • 100%-я интернет-зависимость. При создании Activity объект GoogleApiClient сразу же начинает обмен данными с AS — очевидно, если у пользователя не будет доступа к сети, он не сможет зайти в свою учетную запись. Не забываем и про возможную сегментацию: в мире много мест, где есть интернет, но нет Google.
  • Правила игры могут поменяться. Сколько раз уже случалось, что популярный сервис или API внезапно исчезал или для него менялись правила доступа. К примеру, сервис GCM, о котором мы недавно писали, похоже, может не дожить до конца года: Google просит разработчиков переходить на технологию Firebase Cloud Messaging.
  • У приложения нет своих пользователей. С OAuth в чистом виде разработчик теряет представление о том, кто же вообще интересуется его программой. Конечно, можно после успешной авторизации дублировать данные на свой сервер, но они будут неполными и не всегда актуальными.

Реализация

Теоретическая часть закончена, запускаем Android Studio. OAuth сейчас очень популярна, на сайте проекта указано больше десяти крупных проектов, поддерживающих эту технологию. Как ты уже мог догадаться, общий принцип работы в каждом случае одинаков, различаются только названия классов и адреса удостоверяющих центров.

Поскольку мы пишем под Android, логично будет включить в наше приложение авторизацию через аккаунты Google — почти у каждого пользователя этой ОС есть такая учетная запись. Я знаю только одного человека, который не пользуется Google Play, но он параноик, а OAuth вообще не для них :).

Устройство oauth

С появлением технологии OAuth алгоритм регистрации на новом ресурсе для пользователя резко изменился. Теперь не надо заучивать очередные учетные данные, а можно несколькими нажатиями войти на сайт с помощью своей учетной записи одной из социальных сетей.

Если кратко, то «регистрация» на новом ресурсе с помощью OAuth выглядит так: между пользователем и ресурсом появляется посредник — сервер (чаще всего социальной сети), который получает уведомление пользователя о его намерении, а затем подтверждает ресурсу, что он уже знает этого пользователя и готов поделиться с ресурсом его учетными данными.

Рис. 1. Схема OAuth
Рис. 1. Схема OAuth
1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (1 оценок, среднее: 4,00 из 5)
Загрузка...

Добавить комментарий

Ваш адрес email не будет опубликован.

Adblock
detector