Аутентификация через OAuth2
Чтобы получить доступ к некоторым онлайн-сервисам, пользователям нужно пройти аутентификацию. Обычно он вводит свой логин и пароль в браузере. Если вы создаёте своё приложение, которое будет подключаться к такому сервису, то не только пользователь должен пройти аутентификацию для доступа к сервису в вашем приложении, но и приложение должно быть авторизовано для выполнения действий от имени пользователя.
На данный момент стандартным протоколом для аутентификация является OAuth2 (Open Authorization). OAuth2 обеспечивает единственное значение, называемое токен авторизации (Auth Token), который представляет как пользователя, так и авторизованное приложения, которое будет действовать от имени пользователя.
Протокол используют Twitter, Google, Flickr, Digg, Yahoo и многие другие сервисы.
Необходимо понимать разницу между терминами аутентификация (authentication) и авторизация (authorization).
Аутентификация — вы посещаете режимное предприятие и показываете своё удостоверение. Охранник смотрит и принимает решение: удостоверение настоящее? печать на месте? фотка совпадает? Опытный охранник может узнать вас в лицо и пропустить и так. Аутентификация может быть простой, а может быть и сложной.
На сайтах обычно аутентификация происходит по схеме логин/пароль, заставляя пользователя вводить данные и проверяя их на сервере.
Авторизация наступает после успешной аутентификации. Даже если вы показали настоящее удостоверение, у вас может быть запрет на посещение отдельных этажей, комнат, серверной и т.д. Или вы не можете пройти в здание по выходным.
Авторизация определяет ограничения на доступные действия. Используя OAuth, мы задаём границы доступа пользователя. Например, пользователь может читать личные сообщения, а босс может просматривать переписку любого сотрудника. Причём, открытая аутентификация означает, что мы сами не занимаемся проверкой логина и пароля, а только получаем готовую информацию. Иными словами, организация заключила договор с охранным предприятием, который и поставляет охранников и занимается системой охраны.
Использование OAuth2 подходит для:
- Получения разрешения от пользователя для доступа к онлайн-службе с помощью своей учетной записи.
- Аутентификация на онлайн-сервисе от имени пользователя.
- Обработка ошибок аутентификации.
Чтобы начать использовать OAuth2, вы должны знать:
- URL сервиса, к которому вы хотите получить доступ.
- Рамки аутентификации (auth scope), которые являются строкой, определяющей конкретный тип доступа, запрашиваемый вашим приложением. Например, запрос доступа только для чтения к Google Tasks – это View your tasks, в то время как доступ для чтения и записи к Google Tasks – это Manage Your Tasks.
- Идентификатор клиента (client id) и «секрет клиента» (client secret) – это строки, которые идентифицируют ваше приложение на сервисе. Вы должны получить эти строки непосредственно у владельца сервиса. Google имеет систему самообслуживания для получения идентификаторов клиента и секретов. Статья Getting Started with the Tasks API and OAuth 2.0 on Android объясняет, как использовать эту систему, чтобы получить эти значения для использования c Google Tasks API.
Запрос Auth Token
Теперь вы готовы запрашивать токен авторизации. Это многоступенчатый процесс.
Для получения токена авторизации необходимо сначала запросить права на использование ACCOUNT_MANAGER в манифесте. Не забываем про разрешение на интернет.
Имея необходимые полномочия, вы можете вызвать AccountManager.getAuthToken() для получения токена. Большинство методов AccountManager – асинхронные. Вам придётся реализовать её в виде серии обратных вызовов, например:
В этом примере класс OnTokenAcquired расширяет AccountManagerCallback. AccountManager вызывает run() в OnTokenAcquired с AccountManagerFuture, содержащим Bundle. Если вызов успешен, то токен будет внутри Bundle.
Вы можете получить токен из Bundle:
Если все пойдёт хорошо, то Bundle будет содержать допустимый токен в ключе KEY_AUTHTOKEN. Но не всегда все идёт так как запланированно.
Ваш первый запрос токена авторизации может завершиться неудачей по нескольким причинам:
- Ошибка в устройстве или сети вызвала сбой AccountManager.
- Пользователь решил не предоставлять вашему приложению доступ к аккаунту.
- Сохраненных полномочий аккаунта не хватает для получения доступа к аккаунту.
- Кэшированный токен авторизации истек.
Приложения могут обрабатывать первые два случая, показывая сообщение об ошибке пользователю. Если сеть не работает или пользователь решил не предоставлять доступ, то ваше приложение ничего не может с этим поделать. Последние два случая немного сложнее, потому что хорошо оптимизированные приложения должны обрабатывать эти неудачи автоматически.
Третий случай неудачи, имеющий недостаточные полномочия, передается через Bundle, который вы получаете в вашем AccountManagerCallback (OnTokenAcquired из предыдущего примера). Если Bundle включает Intent в ключе KEY_INTENT, то аутентификатор говорит вам, что ему необходимо прямое взаимодействие с пользователем, прежде чем он может дать вам действительный токен.
У аутентификатора может быть много причин для возврата Intent. Это может быть первый вход пользователя в эту учетную запись. Возможно время учетной записи пользователя истекло и требуется повторный вход в систему или сохраненные полномочия неверны. Может быть аккаунт требует двухфакторной аутентификации либо требуется включение камеры для сканирования сетчатки. В действительности причина не имеет значения. Если вы хотите действительный токен, вы должны обратиться к Intent, чтобы получить его.
Обратите внимание, что пример использует startActivityForResult(), и вы можете перехватить результат Intent в методе onActivityResult() в вашей активности. Если вы не станете перехватывать результат ответа Intent аутентификатора, невозможно будет определить прошёл ли пользователь проверку подлинности или нет. Если результат RESULT_OK, то аутентификатор обновил сохранённые полномочия, чтобы они были достаточны для доступа который вы запросили, далее вы должны вызвать AccountManager.getAuthToken() снова и запросить новый токен аутентификации.
Последний случай, когда токен истёк, на самом деле не ошибка AccountManager. Единственный способ выяснить действительно ли токен истек – это связаться с сервером, что было бы расточительным и дорогим со стороны AccountManager постоянно выходить в интернет и проверять состояние всех своих токенов. Так что эта неудача может быть обнаружено только когда приложение пытается использовать токен аутентификации для доступа к веб сервису.
Подключение к онлайн-сервисам
Пример ниже показывает, как подключиться к серверу Google. Так как Google использует стандарт протокола OAuth2 для проверки подлинности запросов, методы, обсуждаемые здесь широко применимы. Имейте в виду, что каждый сервер отличается. Вам может понадобиться внести небольшие изменения в этот пример для учета вашей конкретной ситуации.
Google API требует предоставления четырех значений с каждым запросом: API key, client ID, client secret, и auth key. Первые три получаются на сайте Google API Console. Последнее значение вы получаете вызвав AccountManager.getAuthToken(). Вы передаете их на сервер Google в качестве части HTTP-запроса.
Если запрос возвращает HTTP-ошибку 401, то ваш токен был отклонен. Самая распространённая причина этого – истечение токена. Исправить это просто: вызовите AccountManager.invalidateAuthToken() и повторите запрос токена еще раз.
Так как истечение токена является обычным явлением и исправление этой ситуации достаточно легкое, многие приложения просто предполагают истечение токена ещё до того как станет известно об этом. Если обновление токена дешёвая операция для вашего сервера, вы можете вызывать AccountManager.invalidateAuthToken() перед первым вызовом AccountManager.getAuthToken() и избавить себя от необходимость запрашивать токен аутентификации два раза.
В будущем мы рассмотрим практические примеры для OAuth.
Источник
Аутентификация OAuth2 в приложении посредством Google Sign-In. Непрерывный доступ к API Google
«С 20 апреля 2017 года отправка запросов на авторизацию из встроенных браузеров будет блокироваться».
Такое сообщение с 1 марта можно увидеть в некоторых приложениях, где необходима авторизация. Об этом Google написали в своём блоге еще в августе 2016, и это значит, что скоро во многих приложениях придется переписывать реализацию регистрации. Приятного мало, однако выход есть – использовать рекомендуемый способ авторизации Google Sign-in.
Об этом способе и будет идти речь в уроке, а также как получить токены, необходимые для работы с API Google.
В уроке будет присутствовать частичный перевод официальных документаций. Но сперва немного предыстории из моей практики и первой работы с OAuth2, возможно кто-то окажется в похожей ситуации.
Понадобилось мне для приложения получить чат с прямой трансляции YouTube. И тогда я узнала, что для отправки запросов на получение трансляции (и только потом чата) необходимо провести OAuth2 аутентификацию пользователя. Я начала искать. Информации по такой теме очень мало, она разрознена, не подходила для моего случая, и конечно же всё было на английском языке. В основном информация была для работы с наиболее популярными API: Drive, Cloud, Google Plus. В официальной документации API YouTube есть готовый код, бери да пользуйся, однако для Android он не подходит. Потратив немалое количество времени, методом проб и ошибок я пришла к рабочему решению. Первое, что мне захотелось сделать после, это собрать информацию «в кучу» и разложить по полочкам, что и сподвигло на написание этого урока.
Изначально моё решение начиналось с того, что перед пользователем открывался WebView для авторизации (ввод email, пароля). Далее запрашивалось разрешение на использование данных, и только после разрешения в ответе приходил код аутентификации (AuthCode), подробнее что с ним делать будет далее. Url, который открывался в WebView был следующий:
Это ни что иное, как 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» можно поменять email и название продукта — это то, что будет написано, когда будет запрашиваться разрешение «Приложение **** запрашивает: …»
2. Настройка Sign-In клиента
Чтобы получить доступ к Google Api Client, в файл gradle app нужно добавить в зависимости:
И плагин (в конец файла):
В файл gradle project в зависимости:
Здесь наибольший интерес вызывают строки:
requestServerAuthCode(getString(R.string.server_client_id)) – запрашиваем authCode, передавая параметр идентификатор клиента (весь полностью), который получили выше.
requestScopes(new Scope(«***»)) – запрашиваем необходимую для используемого API область/области доступа. Есть некоторые уже определенные области в Scopes, но, если нужной там не нашлось, можно задать свою, как в моём случае. Для пользователя будет отображаться как доступ «к чему» хочет получить приложение.
Тут всё по стандарту из документации:
enableAutoManage(this, this) – в параметры передается активити и слушатель соединения (реализуем интерфейс GoogleApiClient.OnConnectionFailedListener).
addApi(Auth.GOOGLE_SIGN_IN_API, gso) – указываем, что используем Sign In api и ранее созданный объект опций.
Теперь для запуска авторизации нужна кнопка (либо любой другой элемент). Можно использовать стандартную кнопку Sign In Button. Разметка xml:
Выглядит она так:
В активити кнопка определяется как и все другие view, на нее повешаем слушатель и по клику выполним метод:
Код вызываемого метода представляет собой создание интента и вызов активити для авторизации:
В параметр передаем сконфигурированный mApiClient. RC_AUTH_CODE любое число, как и всегда, для отслеживания результата активити.
При нажатии на копку, будет предложено выбрать аккаунт для входа, либо добавить новый. После выбора, приложение запросит разрешение:
3. Получение Auth code
После того, как пользователь даст разрешение, в onActivityResult получаем данные его аккаунта:
В результате получаем auth code как обычную строку, выглядит он примерно так:
Так же из аккаунта можно получить 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.
Рассмотрим подробнее параметры. В Request.Builder() Передаем url по которому получаем токены:
В header указываем Content-Type:
Указываем, что это метод POST, в него передаем body:
Сформированный 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» – токен доступа, ради которого всё проводилось
«expires_in» – время жизни access токена, по умолчанию токен живет 1 час, а в сутки можно получать по запросу 25 токенов, не более.
«token_type» – тип токена, его тоже необходимо запомнить, он также вставляется в запрос к api в дальнейшем.
«refresh_token» – токен для обновления access токена, когда пройдет час жизни. Refresh токен неизменен. Часто на форумах видела проблему, с которой сталкивалась и сама: в запросе не приходил этот токен. Ошибки заключаются в неправильном получении учетных данных, либо неправильные запросы. Если авторизация проводилась через WebView, и в url не указывался такой важный параметр как access_type=offline, то refresh токен попросту не приходил.
5. Обновление Access токена
Час прошел, access токен больше не активен, необходим новый. После этого посыпятся ошибки 401 или 403, сервер скажет, что пользователь не авторизован или не имеет доступа. Запрашивать новое разрешение не годится, если нам нужна непрерывная сессия, например как у меня, нужно непрерывно получать сообщения из чата в течении трансляции, а это несколько часов. Что делать? Посылать запрос на получение нового токена.
Запрос в основном такой же как в пункте 4, за исключением некоторых параметров:
Здесь важные параметры:
«grant_type», «refresh_token» – в типе указываем что посылаем refresh токен
«refresh_token», mRefreshToken – и сам токен
Ответом будет json, содержащий новый access токен, с которым снова можно обращаться к API:
На этом авторизация и аутентификация пользователя завершена.
Для примера покажу как выглядит запрос к API, а также как я выполняю обновление токена.
Для запроса к API я использую Retrofit2 + RxAndroid. Так выглядит запрос на получение чата прямой трансляции:
Здесь важно заметить, что в header по ключу Authorization должны передаваться тип токена и сам access токен. То есть так:
Далее делаю запрос через RxAndroid, и так как в коллбэк onError приходят всевозможные ошибки, то туда же приходит ошибка HttpException с кодом 401 Unauthorized по истечении часа. Здесь же я обрабатываю её, проверяю, если это та самая ошибка, то привожу к соответствующему типу, проверяю действительно ли это код 401 и выполняю метод получения нового токена, затем повторяю запрос.
Так же для проверки токена существует GET запрос:
В ответ придут данные о токене, если он еще активен, либо ошибка, если его время жизни истекло.
Опять же, реализацию обновления токена Google оставляет на разработчика.
Выйти из аккаунта и отозвать авторизацию намного проще, в официальной документации найти не составит труда, если понадобится.
Рекомендую перед началом работы проверять запросы в стороннем приложении/расширении, например Postman, чтобы убедиться в правильности ввода параметров и полученных ответах. Я буду очень рада, если кому-то урок окажется полезным!
Источник