Аутентификация OAuth2 в приложении посредством Google Sign-In. Непрерывный доступ к API Google

Ваш аккаунт хранит ваши приложения, музыку, e-mail, книги, документы, кредитные карты и прочий стаф. Настало время защитить его чем-то больше, нежели привычным всем паролем. Если вы следите за новостями мира технологий, то должны быть в курсе о ситуации с Метом Хонаном, который подвергся атаке хакеров, приведшей к уничтожению его iCloud , Twitter и Google аккаунтов и блокированию нескольких устройств. В случае с Хонаном атака была возможна благодаря скомпрометированным личным данным (находящимся в открытом доступе) и ошибкам со стороны служб поддержки Amazon и Apple , а не контактом с вредоносным ПО. Основной причиной того, что хакеры атаковали не только аккаунты и устройства, но и и Google стаф, был тот факт, что Хонан не пользовался двухступенчатой авторизацией. Истории, подобные этой, всегда наталкивают на мысли о мерах предосторожности, и наиболее эффективным в этой ситуации будет использование двухступенчатой авторизации . Внутри видео презентация и подробное описание включения и настройки двухступенчатой авторизации на вашем Google аккаунте!

Что такое двухступенчатая авторизация?

Двухступенчатая авторизация дает дополнительный уровень защиты, запрашивая у вас шестизначный код, сгенерированный Google и отправленный на ваш телефон в момент входа в аккаунт. Это означает, что если ваш пароль взломали, то аккаунт остается нетронутым. Для дальнейшего проникновения необходим будет ваш мобильный. Мы можете получать шестизначный код через СМС или, если вы пользователь Android , BlackBerry , iPhone , из приложения Google Authenticator , которое вы можете использовать для моментального генерирования кода. Эти приложения работают, получая доступ к вашему Google аккаунту и сканируя секретный баркод.

А как быть с Android девайсами и приложениями?

Иногда приложение или девайс, использующие ваш Google аккаунт, не запрашивают код, или это непрактично/нежелательно. Рассмотрим пример с Android устройствами. Вход с таких устройств с включенной двухступенчатой авторизацией приводит к использованию пароля для конкретного приложения. Такой пароль дает возможность доступа устройству или приложению к вашим Google аккаунтам. Получит его можете здесь: accounts.google.com > "Security" > "Authorizing applications and sites". Это займет немного времени и сил, но иначе никак. Например, вы являетесь обладателем смартфона Galaxy Nexus и планшета Nexus 7 . Для каждого из устройств вы создаете по такому паролю и единоразово вводите в настройках каждого из устройств. Если же вам надо прекратить доступ к Google аккаунтам по какой-либо причине, то вы просто нажимаете "revoke" рядом с названием устройства. А так как пароль этот шестнадцатизначный и может быть использован только для одного устройства или приложения, то это действительно весьма безопасно.

Непредвиденные обстоятельства

Также вы получаете дополнительные коды, каждый из которых поможет войти в аккаунт (использоваться может только один раз);

- Google Authenticator не нуждается в интернет доступе для создания кода.

Промах служб поддержки Amazon и Apple (вместе с отсутствием двухшаговой безопасности у iCloud ) привел к тому, что iPad , iPhone и Macbook Мета вышли из строя. Однако, двухступенчатая авторизация могла бы спасти его Google и связанный с ним Twitter аккаунты. Предположим, что вы не пользуетесь данным типом защиты. Если вы забыли пароли и хотите восстановить его, то для этого необходимо будет совершить несколько действий, одним из которых будет получение письма на почту, адрес которой вы указали ранее. Эта процедура является лишь частично скрытой, что позволило хакерам взломать аккаунт Мета. В этом и кроется очень большой промах службы поддержки Amazon и Apple . Если бы пользователь включил двухступенчатую авторизацию, то хакеры, при попытке восстановления пароля, увидели бы следующее сообщение:

Журналисты, а особенно задействованные в сфере технологий, не являются примером обычных пользователей различных аккаунтов, если ваше имя не облетело все просторы интернета, то и вероятность стать жертвой хакеров значительно меньше. Тем не менее, это очень простая и эффективная мера предосторожности для всех. Если вы серьезно связаны с экосистемой Google , то лучше перестраховаться. В противном случае взломщики могут получить доступ к вашей виртуальной жизни, и не только контролировать, а и лишить вас всех приобретений (например, если вы продвинутый Android пользователь и собрали большое количество стафа).

И так, чтобы избежать незапланированных неудобств, пожалуйста, включите двухступенчатую авторизацию Google аккаунта.

Android от А до Я: Двухступенчатая авторизация Google аккаунта:
рейтинг 80 из 80 на основе 80 оценок.
Всего 80 отзывов.

«С 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 был следующий:

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» можно поменять 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 и ранее созданный объект опций.


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

В активити кнопка определяется как и все другие 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 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, чтобы убедиться в правильности ввода параметров и полученных ответах. Я буду очень рада, если кому-то урок окажется полезным!

Знали ли Вы, что используя Google , можно сделать на своем сайте регистрацию на сайте в один клик? Каждый, у кого есть аккаунт в Google, может просто нажать на кнопку, а скрипт автоматически загрузит в приложение e-mail, имя и фотографию.
Используя этот вариант входа/регистрации, вы создадите удобство для пользователей. Вот некоторые из преимуществ:

Не нужно делать проверку формы входа, и вообще создавать форму;

Нет необходимости в функции «Забыл пароль»;

E-Mail адресы уже подтверждены Google, поэтому вам не нужно высылать подтверждение.

Конечно, это будет работать, только если у человека есть аккаунт Google, поэтому имеет смысл сделать что-то в дополнение к этой системе регистрации.

Настройка приложения

Первый шаг заключается в создании приложения через консоль Google API . Следуйте инструкциям для получения дополнительной информации. После завершения процесса, разместить полученные ключи в setup.php.
Запуск кода из schema.sql (вы можете найти его в архиве скачать) в PhpMyAdmin или ином административном MySQL инструменте. Это создаст glogin_users таблицу в базе данных, которая будет использоваться для хранения информации об учетных записях пользователей вашего приложения. После этого, напишите свои данные подключения к базе данных в setup.php .

PHP

Для чтения и вставки в базу данных используется библиотека Idiorm . Будем создавать следующие PHP страницы:

index.php – главный файл приложения;

setup.php – соединение с базой данных и ключи выданные Google;

Папки библиотеки Idiorm, PHP библиотеки Google.

Давайте взглянем на код первой части index.php :

200?"200px":""+(this.scrollHeight+5)+"px");">require "setup.php";

// Create a new Google API client
$client = new apiClient();
//$client->setApplicationName("Tutorialzine");

// Configure it
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setDeveloperKey($api_key);
$client->setRedirectUri($redirect_url);
$client->setApprovalPrompt(false);
$oauth2 = new apiOauth2Service($client);

// The code parameter signifies that this is
// a redirect from google, bearing a temporary code
if (isset($_GET["code"])) {

// This method will obtain the actuall access token from Google,
// so we can request user info
$client->authenticate();

// Get the user data
$info = $oauth2->userinfo->get();

// Find this person in the database
$person = ORM::for_table("glogin_users")->where("email", $info["email"])->find_one();

If(!$person){
// No such person was found. Register!

$person = ORM::for_table("glogin_users")->create();

// Set the properties that are to be inserted in the db
$person->email = $info["email"];
$person->name = $info["name"];

If(isset($info["picture"])){
// If the user has set a public google account photo
$person->photo = $info["picture"];
}
else{
// otherwise use the default
$person->photo = "assets/img/default_avatar.jpg";
}

// insert the record to the database
$person->save();
}

// Save the user id to the session
$_SESSION["user_id"] = $person->id();

// Redirect to the base demo URL
header("Location: $redirect_url");
exit;
}

// Handle logout
if (isset($_GET["logout"])) {
unset($_SESSION["user_id"]);
}

$person = null;
if(isset($_SESSION["user_id"])){
// Fetch the person from the database
$person = ORM::for_table("glogin_users")->find_one($_SESSION["user_id"]);
}



HTML

Код HTML занимает нижнюю половину страницы index.php . После авторизации через Google сюда мы сможем выводить данные о пользователе. Сама страница является стандартным документом HTML5.


200?"200px":""+(this.scrollHeight+5)+"px");">



Google Powered Login Form | Tutorialzine Demo






Login Form