Сайт о телевидении

Сайт о телевидении

» » OAuth: описание протокола простым и понятным языком. Создание учетных данных OAuth2. Процесс с учётными данными клиента

OAuth: описание протокола простым и понятным языком. Создание учетных данных OAuth2. Процесс с учётными данными клиента

OAuth 2 это фреймворк для авторизации, который позволяет приложениям получать ограниченный доступ к аккаунтам пользователей через HTTP , таким как Fackebook , GitHub и DigitalOcean . Он работает путем делегирования идентификации пользователя сервису, на котором размещена учетная запись пользователя и авторизационным сторонним приложения имеющим доступ к учетной записи пользователя. OAuth 2 предоставляет авторизационные потоки для десктопных, веб приложений и мобильных устройств.

Это информационное руководство направлено на разработчиков приложений и предоставляет обзор ролей OAuth 2 , типов авторизованных разрешений, используемых ситуаций и потоков.

Давайте начнём с ролей OAuth 2 !

Роли OAuth

Oauth определяет 4 роли:

  • Владелец ресурса
  • Клиент
  • Сервер ресурса
  • Сервер авторизации

Мы разберём каждую роль в следующих подразделах.

Владелец ресурса : Пользователь

Владелец ресурса это пользователь, который использует приложение для доступа к аккаунту. Доступ приложения к аккаунту пользователя ограничен областью авторизационных разрешений (например чтением или записью).

Сервер ресурса /авторизации : АПИ

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

С точки зрения разработчиков приложений, АПИ сервиса выполняет обе роли, как сервера ресурса так и сервера авторизации. Мы будем перенаправлять к обоим из этих комбинированных ролей, как роль Сервиса так и роль АПИ.

Клиент : Приложение

Клиент это приложение, которое хочет получить доступ к аккаунту пользователя. Перед тем, как оно это сделает, оно должно быть разрешено пользователем и авторизация должна быть проверена АПИ.

Теперь у вас есть представление о том, что из себя представляет роли OAuth, давайте взглянем на диаграмму того, как они в основном взаимодействуют друг с другом:

Вот более подробное объяснение шагов на диаграмме:

  1. Приложение запрашивает от пользователя разрешение на доступ к ресурсам сервиса
  2. Если пользователь разрешил запрос, то приложение получает предоставление авторизации
  3. Приложение запрашивает маркер доступа от сервера авторизации (АПИ сервиса) предоставив удостоверение его подлинности и предоставление авторизации
  4. Если идентичность приложения подлинна и предоставление авторизации действительно, то сервер авторизации (АПИ) выдаёт приложению маркер доступа. Авторизация завершена.
  5. Приложение запрашивает ресурс от сервера ресурса (АПИ) и предоставляет маркер доступа для подтверждения подлинности
  6. Если маркер доступа действителен, сервер ресурса (АПИ) передаёт ресурс приложению

Фактический поток этого процесса будет отличаться в зависимости от типа предоставления авторизации находящегося в использовании — это общая идея. Мы будем изучать различные типы предоставления в следующем разделе.

Регистрация приложения

Перед использованием OAuth в Вашем приложении, Вы должны зарегистрировать свое приложение с сервисом. Это делается через регистрационную форму в разделе Разработчика или АПИ веб-сайта сервиса, где Вы будете предоставлять следующую информацию (и вероятно детальную информацию о Вашем приложении):

  • Название приложения
  • Сайт приложения
  • Перенаправление URI или URL обратного вызова

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

Идентификатор клиента и секретный код клиента

Как только ваше приложение зарегистрировано, сервис будет выдавать «учетные данные клиента» в виде идентификатора клиента и секретного кода клиента. Идентификатор клиента это публичная строка, которая используется АПИ сервиса для идентификации приложения, а так же используется для построения авторизационных URL’ов, которые представляются пользователям. Секретный код клиента используется для установки подлинности приложения для АПИ сервиса, когда приложение запрашивает доступ к аккаунту пользователя и должен быть сохранен в секрете между приложением и АПИ.

Предоставление авторизации

В абстрактном потоке протокола выше, первые четыре шага описывают получение предоставления авторизации и маркера доступа. Тип предоставления авторизации зависит от метода используемого приложением для запроса авторизации и типами предоставления поддерживающимися АПИ. OAuth 2 определяет 4 типа предоставления, каждый из которых по своему полезен в разных ситуациях:

  • Код авторизации : используется вместе с приложениями на стороне сервера
  • Неявность (скрытость) : используется вместе с мобильными приложениями или веб-приложениями (приложения, которые запускаются на устройстве пользователя)
  • : используется вместе с доверенными приложениями, такими как те, что находятся в собственности самого сервиса
  • Учётные данные клиента : используется с приложениями доступа к АПИ

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

Тип предоставления : Код авторизации

Тип предоставления кода авторизации является наиболее используемым так как он оптимизирован под приложения на стороне сервера, где исходный код не показан открыто и конфиденциальность секретного кода клиента может быть сохранена. Это поток на основе перенаправления, что означает что приложение должно быть способно взаимодействовать с юзерагентом (т.е. с браузером пользователя) и получать коды авторизации АПИ, которые направляются через юзерагент.

Шаг 1: Ссылка кода авторизации

  • https://cloud.digitalocean.com/v1/oauth/authorize — конечная точка авторизации API
  • response_type=code — предусматривает что ваше приложение запрашивает предоставление кода авторизации
  • client_id=CLIENT_ID — идентификатор клиента приложения (значение по которому АПИ определяет приложение)
  • redirect_uri=CALLBACK_URL — место куда сервис перенаправляет браузер после того как код авторизации предоставлен
  • scope=read — определяет уровень доступа, который запрашивает приложение

Когда пользователь кликает (нажимает на) по ссылке, они должны сначала залогиниться (войти) в сервисе, для удостоверения их личности (если конечно они уже не залогинились). Затем сервис запросит у них подтверждение на разрешение или запрет доступа приложения к их аккаунту. Вот пример запроса на доступ к аккаунту приложением:

Авторизация приложения

Обзор разрешений (прав доступа)

  • Чтение

Авторизовать приложение Запретить

Если пользователь нажимает на «Авторизовать приложение», то сервис перенаправляет браузер на URI переадресации приложения, который был указан при регистрации клиента, вместе с кодом авторизации. Переадресация будет выглядеть так (предполагая, что адрес приложения «dropletbook.com»):

Шаг 4: Приложение получает маркер доступа

Приложение запрашивает маркер доступа от АПИ передавая код авторизации конечной точке маркера АПИ, вместе с подробной информацией об идентификации, включая секретный код клиента. Вот пример запроса через POST к конечной точке маркера DigitalOcean:

Шаг 5: Приложение получает маркер доступа

{«access_token»:»ACCESS_TOKEN»,»token_type»:»bearer»,»expires_in»:2592000,»refresh_token»:»REFRESH_TOKEN»,»scope»:»read»,»uid»:100101,»info»:{«name»:»Mark E. Mark»,»email»:»[email protected] «}}

Теперь приложение авторизовано! Оно может использовать маркер для доступа к аккаунту пользователя через АПИ сервиса, с ограничениями по доступу, пока маркер не истечёт или не будет отменён. Если маркер обновления был передан, то он может быть использован для запроса новых маркеров доступа, если срок действия исходного маркера истёк.

Тип предоставления : Неявность (скрытость)

Тип неявного предоставления используется для мобильных приложений и веб-приложений (т.е. приложений, которые запускаются в браузере), где конфиденциальность секретного кода клиента не гарантируется. Тип неявного предоставления также является потоком на основе переадресации, но маркер доступа даётся браузеру для передачи его приложению, так что он может быть доступен как пользователю, так и другим приложениям на устройстве пользователя. Кроме того, этот поток не проверяет подлинность идентичности приложения и полагается на перенаправление URI (который был зарегистрирован с сервисом), чтобы служить этой цели.

Тип неявного предоставления не поддерживает маркеры обновления.

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

С типом неявного предоставления, пользователю даётся ссылка авторизации, которая запрашивает маркер от АПИ. Эта ссылка выглядит как ссылка кода авторизации, за тем лишь исключением, что она запрашивает маркер вместо кода (обратите внимание, что тип запроса “маркер”):

Примечание

Когда пользователь нажимает на ссылку, они сначала должны войти в сервис, чтобы подтвердить подлинность их личности (если они ещё не залогинились). Затем им будет предложено сервисом разрешить или запретить доступ приложения к их аккаунту. . Вот пример запроса на доступ к аккаунту приложением:

Авторизация приложения

Thedropletbook хотел бы получить разрешение на доступ к вашей учетной записи

Обзор разрешений (прав доступа)

  • Чтение

Авторизовать приложение Запретить

Мы видим что ”Thedropletbook App” запрашивает разрешение на доступ к чтению аккаунта “[email protected] ”.

Шаг 3: Браузер получает маркер доступа с переадресацией URI

Шаг 4: Браузер следует за переадресацией URI

Браузер следует за переадресацией URI, но при этом сохраняет маркер доступа.

Шаг 5: Приложение отправляет маркер доступа скрипту извлечения

Приложение возвращает страницу, которая содержит скрипт, который может извлекать маркер доступа, из целого URI передаресации, который сохранил браузер.

Шаг 6: Маркер доступа передаётся в приложение

Браузер запускает предоставленный скрипт и передаёт извлечённый маркер доступа приложению.

Примечание : DigitalOcean не поддерживает в данное время тип неявного предоставления, так что ссылка указывает на воображаемый сервер авторизации на «oauth.example.com».

Тип предоставления : Владелец ресурса пароля учётных данных

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

Поток пароля учётных данных

После того как пользователь передал свои учётные данные приложению, после этого оно должно запросить маркер доступа у сервера авторизации. POST запрос может выглядеть как-то так:

Если учётные данные пользователя прошли проверку, сервер авторизации возвращает маркер доступа приложению. Теперь приложение авторизовано!

Примечание : DigitalOcean не поддерживает в данное время тип предоставления пароля учётных данных, так что ссылка указывает на воображаемый сервер авторизации на «oauth.example.com».

Тип предоставления : Учётные данные клиента

Тип предоставления учётных данных клиента предоставляет приложению способ доступа к своему личному аккаунту сервиса. Примеры того, когда это может быть полезным, если приложение хочет обновить своё зарегистрированное описание или URI переадресации, или обратиться к другим данным сохранённым в аккаунте сервиса через АПИ.

Поток учётных данных клиента

Приложение запрашивает маркер доступа отправляя свои учётные данные, свои идентификатор клиента и секретный код клиента, серверу авторизации. Пример POST запроса может выглядеть следующим образом:

Если учётные данные клиента прошли проверку, сервер авторизации возвращает приложению маркер доступа. С этого момента приложению разрешено использовать её собственный аккаунт!

Примечание : DigitalOcean не поддерживает в данное время тип предоставления учётных данных клиента, так что ссылка указывает на воображаемый сервер авторизации на «oauth.example.com».

Пример использования маркера доступа

Как только у приложения появился маркер доступа, оно может использовать маркер для доступа к акаунту пользователя через АПИ, с ограничениями по доступу, пока маркер не истечёт или не будет отменён.

Вот пример АПИ запроса использующего curl . Обратите внимание на то, что он включает маркер доступа:

curl -X POST -H «Authorization: Bearer ACCESS_TOKEN» «https://api.digitalocean.com/v2/$OBJECT» ;

Предполагая, что маркер доступа действителен, АПИ будет обрабатывать запрос в соответствии с своими техническими условиями. Если срок маркера доступа истёк или в противном случае недействителен, АПИ вернёт ошибку “неверный_запрос”.

Поток маркера обновления

После того как срок маркера доступа истёк, использование его для совершения запрос к API приведет к ошибке «Недействительный маркер». На этой стадии, если маркер обновления был включён в запрос, когда исходный маркер доступа был выпущен, он может быть использован для запроса свежего маркера доступа у сервера авторизации. Вот пример POST запроса, использующего маркер обновления для получения нового маркера доступа:

Заключение

На этом мы завершает руководство по OAuth 2. Теперь вы должны иметь хорошее представление о том, как работает OAuth 2 и когда конкретный поток авторизации следует использовать.

Если вы хотите узнать больше об OAuth 2, проверьте эти полезные ресурсы:

  • Как использовать идентификацию OAuth с DigitalOcean будучи в качестве пользователя или разработчика
  • Как использовать АПИ DigitalOcean версии 2
  • Фреймворк авторизации

Шаг 1. Регистрация сайта

Переходим по ссылке http://api.mail.ru/sites/my/add и регистрируем наш сайт. Далее скачиваем и заливаем на ftp файл receiver.html, который нужен для верификации нашего сайта.
После регистрации нам выдадут 3 параметра:

  • Приватный ключ
  • Секретный ключ

Нас будет интересовать только ID и Секретный ключ (Приватный ключ нужен для модели клиент-сервер). Сохраним их в конфигурационном файле нашего скрипта под следующими именами.

$APP_ID; $APP_SECRET;

Шаг 2. Получение Code

Для получения Code нужно обратиться по адресу connect.mail.ru/oauth/authorize , передав ему $_GET параметры:

  • client_id - ID сайта
  • response_type - 3 варианта на выбор. token - доступ к API будет предоставлен только через javascript, code_and_token - доступ к API через сервер и javascript, code - доступ к API через сервер. В нашем случае это будет code .
  • redirect_uri - Адрес страницы получения response_type
  • scope - Привилегии приложения. Параметр необязателен для заполнения и в нашем случае избыточен. Если вы решили запросить привилегии, то учтите, что далеко не все пользователи захотят передать вам их приватные данные, поэтому просто для авторизации параметр scope использовать не надо.

Для удобства разделим логику генерации ссылки на получение code и его обработки:
example.com/login.php - генерируем ссылку для получения code (по-сути ссылка для авторизации).
example.com/auth.php - обработка code.

Итоговый запрос будет выглядеть так:

$redirect_uri = urlencode("http://example.com/auth.php"); $login_url = "https://connect.mail.ru/oauth/authorize?client_id={$APP_ID}&response_type=code&redirect_uri={$redirect_uri}";

Перейдя по этому адресу неавторизованный на Mail.ru пользователь увидит:

Если пользователь авторизован на Mail.ru, то он увидит такое же окно, но без ввода логина и пароля.
Теперь у пользователя 2 варианта действий: разрешить и запретить. Запретив, пользователь будет перенаправлен на страницу указанную в redirect_uri с указанием ошибки.

Если всё прошло как надо и пользователь разрешил сайту доступ к своим данным, то пользователь будет перенаправлен на страницу redirect_uri (http://example.com/auth.php), с $_GET параметром code.
Сохраним его под именем

$APP_CODE;

Шаг 3. Получение token и uid

Для этого надо обратиться по адресу connect.mail.ru/oauth/token , передав ему параметры:

  • client_id ($APP_ID)
  • client_secret ($APP_SECRET)
  • grant_type (authorization_code)
  • code ($APP_CODE)
  • redirect_uri ($redirect_uri из шага 2)

Все параметры обязательтны. redirect_uri должен точно совпадать с тем, который мы использовали на 2-ом шаге.

Запрос на получения token может быть выполнен только через POST запрос, поэтому cURL в помощь:

$ch = curl_init(); $url = "https://connect.mail.ru/oauth/token"; $fields = Array("client_id" => $APP_ID, "client_secret" => $APP_SECRET, "grant_type" => "authorization_code", "code" => $APP_CODE, "redirect_uri" => urlencode(redirect_uri)); foreach($fields as $key => $value){ $fields_string .= $key . "=" . $value . "&"; } rtrim($fields_string, "&"); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); $result = curl_exec($ch); curl_close($ch); $arr = json_decode($result, true); //или $arr = (array) json_decode($result). Кстати кто-нибудь знает, есть ли разница? $token = $arr["access_token"]; $uid = $arr["x_mailru_vid"];

Шаг 4. Получаем пользовательские данные

После получения token и uid сайт получает долгожданный доступ к API Mail.ru по адресу www.appsmail.ru/platform/api .
Но не всё так просто, каждый запрос к API должен быть подписан. Подпись (в нашем случае) представляет собой хэш, рассчитанный по алгоритму md5 из отсортированной в алфавитном порядке строки из передаваемых API параметров без разделителя & и Секретного ключа .
Например:

Md5("app_id=423004method=friends.getsession_key=be6ef89965d58e56decdfacb9b62bdaa" . $APP_SECRET);

Mail.ru предусматривает 2 варианта подписи: клиент-сервер и сервер-сервер. Отличие заключается в большей безопасности 2-го подхода. Его мы и будем использовать. Для этого в запросе к API надо указывать secure=1 .

Выполним запрос на получение анкетной информации пользователя Mail.ru, воспользовавшись методом users.getInfo .

$request_params = Array("app_id" => $APP_ID, "uids" => $uid, "method" => "users.getInfo", "secure" => 1, "session_key" => $token); //Параметры обязательно должны быть отсортированы в алфавитном порядке ksort($request_params); $params = ""; foreach ($request_params as $key => $value) { $params .= "$key=$value"; } //В модели сервер-сервер подпись выглядит так $sig = md5($params . $APP_SECRET); //Формируем запрос $url = "http://www.appsmail.ru/platform/api?method=users.getInfo&app_id={$APP_ID}&session_key={$token}&sig={$sig}&uids={$uid}&secure=1"; $response = file_get_contents($url); $info = (array) json_decode($response); $info = $info; //весь результат print_r($info);

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

PROFIT!!1 Всем добра. Если статья окажется интересной, то могу так же разобрать процесс серверной oAuth 2.0 авторизации для Vkontakte и Facebook.

Существует множество способов распространения вредоносного спама во ВКонтакте. Но вредители не дремлют, в их головы приходят все больше интересных идей. И оказалась как никак кстати. Мошенники научились пользоваться им для обхода страницы предупреждения о вредоносных сайтах.

А все началось с того, что однажды на моей стене появилось такое сообщение:


Из любопытства перешел по ссылке и попал на очередной фишинговый сайт. Но мне показалась странной сама ссылка, она имела вид (половина символов в ASCII):
vkontakte.ru/away.php ?to =http%3A%2F%2FApi.vKontakte.Ru%2F%2Fo%2561u%2574%…

Тут-то самое интересное и начинается...
Разберем вторую ссылку по частям:

Что означает каждый из параметров:

  • client_id - ID приложения, требующего авторизацию;
  • redirect_uri - адрес, на который будет передан access_token (посредством редиректа);
  • display - вид окна авторизации (page, popup, touch и wap).
Собственно в redirect_uri содержался адрес фишинг сайта. Так как в параметре display была допущена ошибка (в него шел мусор "?390852"), окно авторизации не выводилось, а сразу шел редирект на фишинг сайт с параметрами: error=invalid_request&error_description=Invalid+display+passed

В этом и заключается весь смысл обхода блек-листа вредоносных сайтов ВКонтакте. Появляется только алерт о переходе на api.vk.com. И в результате перехода мы прямиком попадаем на фишинг сайт, который имеется в черном списке. При переходе по ссылке vkontakte.ru/away.php?to=vgostivk.dyndns**:

Как оказалось, приложение, якобы требующее авторизацию висело на взломанном пользователе:

Да и сам фишинг сайт был устроен довольно интересно. Дизайн по обычаю был контактовский и предлагал авторизоваться. Я прошел авторизацию через рандомные почту и пароль, фейк прекрасно проглотил. Дальше было еще интересней, на главной появилась новость от «Павла Дурова»:

После нажатия на кнопку «Создать персональный счетчик», последовал прекрасный прогресс-бар. Затем было предложено указать свой номер и отправить sms:

По идее после успешной «активации» должно было перекинуть на страницу activ.php, но я не смог попасть туда. Выдержки из JS скриптов фишинг сайта:

...
if (req.status == 200) {
// если статус 200 (ОК) - выдать ответ пользователю
if (req.responseText == "ok" ) {
//statusElem.innerHTML = "Все гуд!";
get_activation();
}
if (req.responseText == "not" ) {statusElem.innerHTML = "Неверный код активации" ;}
//statusElem.innerHTML = "Ответ сервера: "+req.responseText;
...
function get_activation() {
document .location="activ.php" ;
}

* This source code was highlighted with Source Code Highlighter .


Итог : Мошенники используют обход предупреждений через OAuth 2.0, получают пароль и email пользователя, да еще и пытаются развести на отправку sms (скорее всего используя систему подписок).

Популярный протокол, который позволяет социальным сервисам интегрироваться между собой и дает безопасный способ обмена персональной информацией. OAuth может связать между собой 2 сервиса, каждый из которых имеет свою пользовательскую базу - именно их я в данном случае называю «социальными». Когда начинаешь работать с OAuth, первое ощущение - что протокол весьма сложен и избыточен. В этой статье я попытаюсь объяснить основы OAuth человеческим языком.

Пример кросс-авторизации

Вернемся в 2005-й год и представим, что мы пишем социальную сеть. В ней имеется форма импорта контактов из адресной книги GMail. Что нужно для доступа к контактам GMail? Конечно, логин и пароль от ящика. Но если мы попросим ввести их на нашем сайте, пользователь заподозрит неладное. Где гарантия, что мы не сохраняем на сервере введенные пароли? Поэтому нам хочется, чтобы пароль вводился только на сайте GMail , и после этого доступ к контактам через API GMail предоставлялся нашей социальной сети (возможно, на время). Договоримся о терминах.
  • Consumer : потребитель; скрипт обработки формы импорта контактов в социальной сети.
  • Service Provider : поставщик данных; GMail, содержащий в себе данные адресной книги, интересные для Consumer-а.
  • User : пользователь, имеющий аккаунт как у Consumer-а, так и у Service Provider-а.
  • Protected Resource : личные данные; контакты из адресной книги на GMail (т.е. ресурсы Service Provider-а).
  • Provider API : API GMail, позволяющий любому скрипту получить контакты из адресной книги GMail.
Задача OAuth - сделать так, чтобы User имел возможность работать на сервисе Consumer (в соцсети) с защищенными данными Service Provider-а (GMail), вводя пароль к этим данным исключительно на Service Provider-e и оставаясь при этом на сайте Consumer-а. Не так уж и сложно, верно?

Чем OAuth отличается от OpenID?

OAuth часто называют «протоколом для роботов», в отличие от OpenID - «протокола для пользователей». Не путайте их!
  1. OpenID - протокол для ускоренной регистрации. OpenID позволяет пользователю без ввода пароля получить аккаунт на каком-либо сервисе, если он уже зарегистрирован где-то еще в интернете. (И потом можно без ввода пароля входить на сервис, будучи авторизованным «где-то».) Например, если у вас есть аккаунт на Яндексе, вы сможете «входить» с его помощью на любой сервис, поддерживающий OpenID-авторизацию.
  2. OAuth - протокол для авторизованного доступа к стороннему API. OAuth позволяет скрипту Consumer-а получить ограниченный API-доступ к данным стороннего Service Provider-а, если User дает добро. Т.е. это средство для доступа к API.

Милицейская аналогия

Представьте, что вы - сотрудник Уголовного розыска, ищущий концы в деле о краже WebMoney за 1973-й год. Договоримся о терминах:
  • OAuth Consumer : Уголовный розыск.
  • User : сотрудник Уголовного розыска.
  • Service Provider : Картотека архива преступлений.
  1. OpenID: сотрудник Уголовного розыска (User) приходит в Картотеку (Service Provider), предъявляет на входе ксиву (Authorization) и на месте перебирает карточки в поисках информации.
  2. OAuth: сотрудник Уголовного розыска (User) прямо с работы (Consumer) звонит в Картотеку (Service Provider). Он докладывает свою фамилию; если его узнают (Authorization), он просит предоставить список всех преступлений за 1973-й год (API call).
Как видите, OpenID и OAuth - разные вещи. OpenID позволяет вам прямо на месте получить доступ к некоторым ресурсам. OAuth обеспечивает получение части информации с удаленного сервиса через API.

План этой статьи

Прежде чем перейти к основной части, давайте посмотрим, как именно мы будем двигаться.
  1. Рассмотрим проблемы, которые возникают при «ручной» реализации кросс-авторизации.
  2. Поговорим о том, что такое «приложение» и кто такой Consumer.
  3. Коснемся основ криптографии.
  4. Обозначим демо-приложение, которое мы будем писать в этой статье.
  5. Определимся с тестовым сервером OAuth, на котором будем экспериментировать.
  6. Пройдем по всем шагам протокола OAuth и приведем исходники скрипта.

Об изобретении велосипедов

Хороший способ понять что-то - сделать это самому, наступив попутно на все разложенные грабли. Сейчас мы будем изобретать велосипед: попробуем представить, как бы мы решали задачу взаимодействия Consumer-а и Service Provider-а без всяких стандартизированных протоколов.

Во-первых, напишем саму форму импорта контактов с GMail:Далее попросим разработчиков GMail сделать так, чтобы при переходе пользователя по URI /auth.php ему бы выдавалась форма авторизации (в нашем веломире GMail написан на PHP). После успешного ввода пароля пользователь должен редиректиться на сайт, чей URL указан в параметре retpath. Также дополнительно в URL должен передаваться некоторый секретный ключ, который уже можно использовать для доступа к API GMail.

Итак, после ввода пароля пользователь будет возвращаться к нам на сайт по следующему адресу:А мы из скрипта /import.php обратимся к API GMail, передадим в него ключ Y49xdN0Zo2B5v0RR и загрузим контакты:Ну что же, давайте теперь считать грабли (потому что шишки считать будет уже поздно).

Грабли первые: подмена адреса возврата retpath

Ну конечно же, вы догадались, что злоумышленник на своем сайте первым делом разместит ссылкуи заставит вас на нее кликнуть. В результате он получит секретный ключ, который вернул GMail, а значит, и ваши контакты:

Грабли вторые: «подслушивание» секретного ключа

Предположим, мы как-то защитили retpath, и он теперь может указывать только на наш сайт. Но проблема с параметром secret остается.Secret можно подсмотреть из-за спины или перехватить методом прослушивания WiFi-трафика. Или на вашем сайте когда-нибудь найдется XSS-уязвимость, позволяющая «утянуть» секретный ключ. Имея значение secret, злоумышленник сможет прочитать вашу адресную книгу. Значит, нужно обезопасить secret от перехвата (в идеале - вообще его не передавать через URL).

Грабли третьи: слишком много редиректов

Если для каждого вызова API требуется разный secret, то нам придется организовывать столько редиректов на сайт Service Provider-а, сколько у нас вызовов. При интенсивном использовании API это работает очень медленно, да и неудобно порядком…

Грабли четвертые: плохая идентификация Consumer-а

GMail, конечно, хочет знать, кто пользуется его API. Разрешить доступ одним сайтам и запретить - другим… Значит, при формировании запроса в форме импорта контактов Consumer (сайт) должен «представляться» Service Provider-у (GMail-у). В нашем случае эту функцию частично выполняет retpath (имя сайта в нем), но данный способ не универсален, т.к. механизм «представления» должен быть задейстсован еще и при вызове API-методов.

Фундамент OAuth

Примечательно, что «подводных граблей» осталось еще много. Я не буду их здесь описывать, потому что эти грабли лежат в Марианской впадине (глубоко, 10920 м). На описание уязвимостей пришлось бы потратить с десяток страниц. Так что я сразу перейду к описанию OAuth, где все проблемы уже решены.

Приложение = Consumer + доступ к API

При работе с OAuth важно, что термин Consumer не ограничивается смыслом «сайт». Consumer - это некоторое приложение , а уж где оно размещено, не так важно. Примеры Consumer-ов из реальной жизни: Но из одного OAuth каши не сваришь. Действительно, все, что дает OAuth, - это возможность авторизоваться на удаленном сервисе (Service Provider) и делать автризованные запросы к API. Не важно, как устроен этот API: это может быть чистый SOAP, REST-подход т. д. Главное, чтобы каждый метод API принимал на вход специальные параметры, передаваемые согласно протоколу OAuth.

Token = Key + Secret

Один из принципов OAuth гласит, что никакие секретные ключи не должны передаваться в запросах открытыми (выше в примере мы рассматривали, почему). Поэтому протокол оперирует понятием Token. Токен очень похож на пару логин + пароль: логин - открытая информация, а пароль - известен только передающей и принимающей стороне. В терминах OAuth аналог логина называется Key, а аналог пароля - Secret. Ситуация, когда Secret известен только отправителю и получателю, но более никому, называется Shared Secret.

Итак, если Consumer и Provider каким-то образом договорятся между собой о Shared Secret, они могут открыто обмениваться в URL соответствующими ключами (Key), не опасаясь, что перехват этих ключей будет опасен. Но как защитить URL с Key от подделки?

Сообщение = Документ + Цифровая подпись

«Цифровая подпись» - звучит страшно, но на самом деле это достаточно очевидная вещь. Когда вы ручкой подписываетесь на каком-либо документе, вы удостоверяете, что этот документ написан вами, а не кем-то другим. Ваша подпись как бы «добавляется» к документу и идет с ним в «одном комплекте».

Аналогично, цифровая подпись добавляется к некоторому блоку данных, удостоверяя: тот, кто сформировал эти данные, не выдает себя за другого. Цифровая подпись не шифрует документ, она лишь гарантирует его подлинность! Поставить подпись позволяет тот самый Shared Secret, который известен получателю и отправителю, но более никому.

Как это работает? Пусть наш $sharedSecret = 529AeGWg, и мы сообщили его шепотом на ухо принимающей стороне. Мы хотим передать сообщение «Мой телефон 1234567» с гарантированной защитой от фальсификации злоумышленником.

  1. Consumer добавляет цифровую подпись к сообщению, в общем виде - $transfer = $message . "-" . md5($message . $sharedSecret); // $transfer = "Мой телефон 1234567" . "-" . md5("Мой телефон 1234567" . "529AeGWg")
  2. Service Provider принимает данные, разбивает их обратно на 2 части - $message и $signature - и проделывает точно такую же операцию: $signatureToMatch = md5($message . $sharedSecret); // $signatureToMatch = md5("Мой телефон 1234567" . "529AeGWg"); Дальше остается только сравнить получившееся значение $signatureToMatch с тем, что было в полученных данных $signature и рапортовать о подделке, если значения не совпали.

Демонстрация работы OAuth на примере простого приложения

Чтобы «вживую пощупать» OAuth, нам потребуются две вещи:
  1. Скрипт, реализующий клиентскую часть протокола. Я написал как раз такой небольшой PHP-скрипт (ссылка на zip-архив). Это виджет, который можно вставлять на PHP-сайты.
  2. Тестовый сервер OAuth, на котором мы сможем экспериментировать. Для этой цели удобно использовать РуТвит: там есть страница http://rutvit.ru/apps/new , которая позволяет добавить тестовое приложение за 30 секунд. (Кстати, URL возврата в форме можно не указывать - мы все равно передаем его из тестового скрипта.)
Глядя на код демо-скрипта и читая пояснения ниже в статье, можно разобраться с деталями протокола.

Вы можете вставить данный виджет на любой PHP-сайт, просто скопировав его код и подправив верстку. Выводятся все твиты с сервиса РуТвит , помеченные указанным хэш-тэгом, а также имеется возможность добавлять новые твиты (тут-то как раз и задействуется OAuth). Виджет использует API и OAuth-авторизацию РуТвита, которые, кстати говоря, совпадают со стандартом API Twitter-а.Вы можете запустить этот скрипт на своем тестовом сервере. Для этого нужно выполнить три действия:

  1. Скачайте код скрипта и разверните его в любую удобную директорию на веб-сервере.
  2. Зарегистрируйте новое тестовое приложение на OAuth-сервере.
  3. После регистрации приложения замените параметры OA_CONSUMER_KEY и OA_CONSUMER_SECRET в скрипте на значения, полученные от сервера.

Регистрация приложения и его параметры

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


После регистрации приложения вам выдается 5 параметров, которые требуются для работы с OAuth. Вот как они могут выглядеть:


Здесь Consumer key и Consumer secret - это своеобразные «логин + пароль» вашего приложения (помните выше разговор о токенах? это как раз один из них). Напомню, что Consumer secret - это Shared Secret, известный только отправителю и получателю, но никому больше. Остальные 3 значения задают служебные URL, смысл которых мы сейчас рассмотрим.

OAuth = Fetch Request Token + Redirect to Authorization + Fetch Access Token + Call API

В примере с GMail мы использовали 2 вида удаленных вызовов: а) редирект через браузер; б) обращение к API изнутри скрипта.

И мы вскрыли ряд проблем с безопасностью, что наводит на мысль: вызовов должно быть больше. Так и происходит в OAuth: добавляются еще промежуточные запросы от скрипта Consumer-а к Provider-у, оперирующие токенами. Давайте их рассмотрим.

  1. Обработка отправки формы. Это не часть OAuth, а часть нашего приложения. Прежде чем обращаться к API Provider-а, мы должны получить от пользователя заказ-наряд на это действие. Вот пример такого «заказа»:
  2. Fetch Request Token (внутренний запрос).
    • Скрипт Consumer-а обращается к Request token URL Provider-а: например, api.rutvit.ru/oauth/request_token . В запросе передается Consumer key - «логин приложения», а сам запрос подписывается при помощи Consumer secret - «пароля приложения», что защищает его от подделки.
    • В ответ Provider генерирует и возвращает «заполненный мусором» токен, который называется Request Token. Он нам еще пригодится, поэтому мы должны сохранить его где-нибудь - например, в сессионной переменной $S_REQUEST_TOK.
  3. Redirect to Authorization (через редирект в браузере). Теперь у нашего приложения есть уникальный Request Token. Требуется получить у пользователя разрешение на использование этого токена, т.е. попросить его авторизовать Request Token .
    • Consumer редиректит браузера на специальный Authorize URL Provider-а: например, api.rutvit.ru/oauth/authorize . В параметрах передается Request Token Key.
    • Provider выводит форму авторизации для своего пользователя и, если он авторизовался, редиректит браузер назад. Куда именно? А мы указываем это в параметре oauth_callback.
  4. Fetch Access Token (внутренний запрос). Итак, браузер вернулся в наше приложение после серии редиректов. Это значит, что авторизация на Provider-е прошла успешно, и Request Token разрешен к работе. Однако в OAuth для безопасности каждый токен имеет свое собственное, строго ограниченное назначение. Например, Request Token используется только для получения подтверждения от пользователя, и больше ни для чего. Для доступа к ресурсам нам нужно получить новый токен - Access Token - или, как говорят, «обменять Request Token на Access Token».
    • Consumer обращается к Access token URL - например, api.rutvit.ru/oauth/access_token , - и просит выдать ему Access Token взамен имеющегося у него Request Token-а. Запрос подписывается цифровой подписью на основе Request Token secret.
    • Provider генерирует и возвращает Access Token, заполненный «мусором». Он также помечает в своих таблицах, что для этого Access Token-а разрешен доступ к API. Наше приложение должно сохранить у себя Access Token, если оно собирается использовать API в дальнейшем.
  5. Call API (внутренний запрос). Ну что же, теперь у нас есть Access Token, и мы можем передавать его key при вызове методов API.
    • Consumer генерирует запрос к API Provider-а (например, используя POST-запрос согласно REST-идеологии). В запросе передается Access Token Key, а подписывается он при помощи Shared Secret этого токена.
    • Provider обрабатывает API-вызов и возвращает данные приложению.

Конец скрипта: вывод виджета

Окончание скрипта должно быть понятно и без подробных разъяснений.
Листинг 14: Окончание скрипта: вывод виджета
// конец case } } // Получаем все имеющиеся твиты. $text = file_get_contents("http://api.rutvit.ru/search.xml?rpp=5&q=" . urlencode("#" . TAG)); $TWEETS = new SimpleXMLElement($text); // Shortcut для вывода сообщения с перекодировкой и квотингом. function e($text, $quote = 1) { $text = iconv("utf-8", ENCODING, $text); echo $quote? htmlspecialchars($text) : $text; } ?>
status as $tweet) {?>
user->screen_name)?>: text_formatted, 0)?>
?action=form_is_sent" style="margin: 1em 0 0 0">

Полезные ссылки по OAuth

  • rutvit
Добавить метки
  1. Открытие встроенного браузера со страницей авторизации
  2. У пользователя запрашивается подтверждение выдачи прав
  3. В случае согласия пользователя, браузер редиректится на страницу-заглушку во фрагменте (после #) URL которой добавляется access token
  4. Приложение перехватывает редирект и получает access token из адреса страницы
Этот вариант требует поднятия в приложении окна браузера, но не требует серверной части и дополнительного вызова сервер-сервер для обмена authorization code на access token .
Пример
Открываем браузер со страницей авторизации:
> GET /oauth/authorize?response_type=token&client_id=464119 HTTP/1.1 > Host: connect.mail.ru

После того, как пользователь выдаст права, происходит редирект на стандартную страницу-заглушку, для Mail.Ru это connect.mail.ru/oauth/success.html :
< HTTP/1.1 302 Found < Location: http://connect.mail.ru/oauth/success.html#access_token=FJQbwq9&token_type=bearer& expires_in=86400&refresh_token=yaeFa0gu

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

Авторизация по логину и паролю

Авторизация по логину и паролю представляет простой POST-запрос, в результате которого возвращается access token . Такая схема не представляет из себя ничего нового, но вставлена в стандарт для общности и рекомендуется к применению только, когда другие варианты авторизации не доступны.
Пример
> POST /oauth/token HTTP/1.1 > Host: connect.mail.ru > Content-Type: application/x-www-form-urlencoded > > grant_type=password&client_id=31337&client_secret=deadbeef&username=api@corp.mail.ru& password=qwerty < HTTP/1.1 200 OK < Content-Type: application/json < < { < "access_token":"SlAV32hkKG", < "token_type":"bearer", < "expires_in":86400, < "refresh_token":"8xLOxBtZp8", < }
Описание в спецификации

Восстановление предыдущей авторизации

Обычно, access token имеет ограниченный срок годности. Это может быть полезно, например, если он передается по открытым каналам. Чтобы не заставлять пользователя проходить авторизацию после истечения срока действия access token "а, во всех перечисленных выше вариантах, в дополнение к access token "у может возвращаться еще refresh token . По нему можно получить access token с помощью HTTP-запроса, аналогично авторизации по логину и паролю.
Пример
> POST /oauth/token HTTP/1.1 > Host: connect.mail.ru > Content-Type: application/x-www-form-urlencoded > > grant_type=refresh_token&client_id=31337&client_secret=deadbeef&refresh_token=8xLOxBtZp8 < HTTP/1.1 200 OK < Content-Type: application/json < < { < "access_token":"Uu8oor1i", < "token_type":"bearer", < "expires_in":86400, < "refresh_token":"ohWo1ohr", < }