Содержание статьи
Рано или поздно у разработчика мобильных приложений возникает желание каким-то образом выделить каждого пользователя — создать ему личный профиль, дать возможность перенести приложение на новый телефон без потери контента или просто разослать персонифицированную рекламу.
Intro
Основа любой персонализации — это собственный аккаунт для каждого пользователя. Но так уж устроен человек, что мало кто захочет тратить время на скучную регистрацию, — у пользователей уже есть Instagram, Twitter и Facebook, для новых аккаунтов в голове места может и не хватить. Тут даже незачем далеко ходить за примером — загляни в свое сердце :). Представь, что ты пользователь, — на одного тебя в Google Play приходятся десятки полезных приложений, но регистрироваться в каждом из них у тебя наверняка нет никакого желания.
Проблема избыточного количества учетных записей на одного пользователя назрела довольно давно, еще когда интернет только стал достаточно быстрым для комфортного поглощения контента. На пике роста социальных сетей, в 2006 году, разработчики Twitter подумали, что пользователям не обязательно регистрироваться на новых сайтах, — если у них есть личный микроблог, компания может немного поделиться своей базой данных, подтвердив стороннему ресурсу, что учетные данные пользователя указаны верно.
Так появилась технология OAuth — механизм авторизации пользователя на сторонних ресурсах с помощью доверенной третьей стороны. Этот сервис стал чрезвычайно популярным: Instagram, Facebook и многие другие крупные проекты теперь позволяют своим пользователям быстро пройти авторизацию на стороннем ресурсе. Присоединяйся и ты: даже в небольшом проекте сегодня имеет смысл реализовать OAuth — пользователи уже привыкли к этому механизму.
Хороший программист, в отличие от джуниора, хотя бы в общих чертах понимает, что именно он делает, поэтому прежде, чем реализовывать API, разберем, как вообще это все работает.
Замечу, что OAuth пришел в мобильные устройства из Web’а, поэтому, даже если ты далек от Java и Android, информация о том, как устроен такой механизм авторизации, все равно может тебе пригодиться.
Устройство OAuth
С появлением технологии OAuth алгоритм регистрации на новом ресурсе для пользователя резко изменился. Теперь не надо заучивать очередные учетные данные, а можно несколькими нажатиями войти на сайт с помощью своей учетной записи одной из социальных сетей.
Если кратко, то «регистрация» на новом ресурсе с помощью OAuth выглядит так: между пользователем и ресурсом появляется посредник — сервер (чаще всего социальной сети), который получает уведомление пользователя о его намерении, а затем подтверждает ресурсу, что он уже знает этого пользователя и готов поделиться с ресурсом его учетными данными. По сути, в этом длинном предложении уже раскрыт весь смысл технологии, сейчас мы подробно разберем происходящее на примере и с картинками.
Xakep #210. Краткий экскурс в Ethereum
Представим себе небольшое приложение под названием App, где пользователь очень хочет завести себе аккаунт. У него нет желания придумывать новые логин и пароль, но есть страничка в некой социальной сети, поэтому он решает зайти в App с помощью своей учетной записи, нажав на знакомую иконку. Так запускается процесс авторизации, в котором участвует на удивление много сторон, хотя внешне все происходит как будто внутри приложения. Весь процесс можно разбить на шесть этапов.
- Нажав на иконку, пользователь запустил в работу механизм OAuth. App отрисовывает диалоговое окно, в котором социальная сеть просит ввести логин, пароль и подтвердить, что действительно нужно поделиться данными с приложением.
- Подтверждая свои намерения, пользователь незаметно для себя отправляет запрос в центр авторизации (Authorization server, AS) своей социальной сети.
- Если были введены правильные идентификационные данные к аккаунту, AS генерирует уникальный код доступа (Code), который отправляется обратно пользователю.
- Отправленный пользователю Code поступает напрямую в App. С этого момента человек выбывает из процесса обмена данными и диалог происходит между App и AS. Поскольку по созданному Code может работать только одно приложение, App нужно однозначно идентифицировать себя — для этого App отправляет два параметра: Code и собственный уникальный ключ Key.
- Рассмотрев полученные Code и Key, AS решает, допускать ли приложение. Если Сode валиден и App нет в черном списке, то AS генерирует Token для доступа к защищенным данным пользователя (Protected resources, PR) и отправляет его обратно в App. Как правило, PR хранятся на отдельном от AS сервере.
- Имея токен, App теперь может запрашивать все доступные PR: имя пользователя, почтовый адрес и прочее. Этой информации будет достаточно для однозначной и полной идентификации пользователя.
Вот так это и работает. Схема довольно упрощенная, и хочу обратить твое внимание на принципиальную особенность OAuth: эта технология не выполняет аутентификацию пользователя. OAuth не отвечает за валидность пары «логин — пароль» и уж тем более за то, что эти данные вводит именно пользователь! Весь процесс предполагает только авторизацию — то есть приложению предоставляются права совершать действия с пользовательскими данными, хранящимися на стороннем сервере.
При этом аутентификация здесь тоже есть и происходит на этапах 1–3, но протекает незримо для нас с помощью встроенного в OAuth протокола OpenID. В этом протоколе реализовано немного криптографической магии, описание которой потребует нескольких таких статей, поэтому ее мы сегодня затрагивать не будем.
Реализация
Теоретическая часть закончена, запускаем Android Studio. OAuth сейчас очень популярна, на сайте проекта указано больше десяти крупных проектов, поддерживающих эту технологию. Как ты уже мог догадаться, общий принцип работы в каждом случае одинаков, различаются только названия классов и адреса удостоверяющих центров.
Поскольку мы пишем под Android, логично будет включить в наше приложение авторизацию через аккаунты Google — почти у каждого пользователя этой ОС есть такая учетная запись. Я знаю только одного человека, который не пользуется Google Play, но он параноик, а OAuth вообще не для них :).
Библиотека 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'
Генерация ключа
Как и при работе с любым другим API из библиотеки Google Play Services, необходимо добавить в приложение конфигурационный файл, созданный на сайте Google. Обрати внимание, что в этот раз он будет жестко привязан к цифровой подписи устройства, на котором разрабатывается приложение. Поэтому если потом проект с созданным конфигом собрать на другом компьютере, то GSI работать не станет.
Интегрируемся
Поскольку 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();
SignInButton
Чтобы дизайнеры всего мира не мучились, копируя логотип Google, в Google Play Services есть готовая реализация кнопки со знакомым каждому пользователю Android интерфейсом. Без лишних сомнений добавляем ее в верстку UI.
<com.google.android.gms.common.SignInButton
android:id="@+id/sign_in_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
Теперь нужно создать обработчик касания кнопки, который выдаст пользователю окошко с предложением войти в свой Google-аккаунт. Как это обычно бывает в мире Android, приглашение будет выведено с помощью создания Intent’а и запуска нового Activity.
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
startActivityForResult(signInIntent, RC_SIGN_IN);
Чтобы отловить результат «общения» пользователя с AS, понадобится метод onActivityResult
— в него вернется результат запущенного Activity. Прежде чем что-то делать, нужно удостовериться, что завершился именно процесс аутентификации пользователя, — об этом нам скажет код запроса, равный константе RC_SIGN_IN
.
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
handleSignInResult(result);
}
}
Получение новости о том, что пользователь попытался залогиниться, — важный этап, поэтому основные действия переносятся в отдельный метод handleSignInResult
. Нужно будет проверить, удалось ли пользователю войти в свой аккаунт, если да, то можно обращаться к его защищенным данным.
GoogleSignInAccount acct = result.getSignInAccount();
mStatusTextView.setText(acct.getEmail());
ImageView logo;
logo.setImageBitmap(loadByPicasso(acct.getPhotoUrl()));
...
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);
...
}
}
}
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();
Ложка дегтя
Прибегнуть к сторонней базе данных пользователей легко и удобно, но разработчики все равно оставляют пользователю возможность создать новый аккаунт в приложении. И правильно делают, ведь у OAuth есть несколько слабых мест, которые могут быть не так очевидны.
- 100%-я интернет-зависимость. При создании Activity объект
GoogleApiClient
сразу же начинает обмен данными с AS — очевидно, если у пользователя не будет доступа к сети, он не сможет зайти в свою учетную запись. Не забываем и про возможную сегментацию: в мире много мест, где есть интернет, но нет Google. - Правила игры могут поменяться. Сколько раз уже случалось, что популярный сервис или API внезапно исчезал или для него менялись правила доступа. К примеру, сервис GCM, о котором мы недавно писали, похоже, может не дожить до конца года: Google просит разработчиков переходить на технологию Firebase Cloud Messaging.
- У приложения нет своих пользователей. С OAuth в чистом виде разработчик теряет представление о том, кто же вообще интересуется его программой. Конечно, можно после успешной авторизации дублировать данные на свой сервер, но они будут неполными и не всегда актуальными.
Заключение
Сегодня мы разобрались с очень модным инструментом из арсенала современного Frontend-разработчика. OAuth легок в применении, удобен в работе и, самое главное, привычен для пользователя. Конечно, он неидеален, но востребован и имеет множество плюсов. Чтобы ты смог еще легче освоить материал, скачай полный исходный код Activity. Если останутся какие-то вопросы, пиши мне на почту. Удачи!