- •Зарегистрируйте свое веб-приложение в Google.
- •Введение в oAuth
- •Ключевые игроки oAuth
- •Разработка настольного клиентского приложения для Twitter
- •Регистрация настольного приложения на Twitter: MyTtDesktopClient
- •Разработка и тестирование MyTtDesktopClient
- •Листинг 1. Метод updateStatus(String status) класса myttdesktopclient.OAuthTwitterClient
- •Листинг 2. Выходная информация MyTtDesktopClient
- •Листинг 3. Метод updateStatus(int userId, String status) класса myttdesktopclient.OAuthTwitterClient
- •Пример кросс-авторизации
- •Чем oAuth отличается от OpenId?
- •План этой статьи
- •Об изобретении велосипедов
- •Грабли первые: подмена адреса возврата retpath
- •Грабли вторые: "подслушивание" секретного ключа
- •Грабли третьи: слишком много редиректов
- •Грабли четвертые: плохая идентификация Consumer-а
- •Фундамент oAuth
- •Демонстрация работы oAuth на примере простого приложения
- •Регистрация приложения и его параметры
- •Fetch Request Token (внутренний запрос).
- •Конец скрипта: вывод виджета
- •Полезные ссылки по oAuth
Механизм настройки для управления маркерами OAuth.
После того как ваше приложение получит от YouTube маркер доступа OAuth, оно будет использовать этот маркер для запросов API YouTube, выполняемых от имени пользователя, связанного с этим маркером. Поэтому вашему приложению потребуется хранить маркеры и отслеживать пользователей, для которых действителен каждый маркер. Ваше приложение не должно пытаться получить новый маркер доступа каждый раз, когда ему требуется взаимодействовать с YouTube от имени определенного пользователя. В действительности маркеры следует защищать так же тщательно, как и другую важную информацию пользователя, которая сохраняется вашим приложением.
Использование OAuth
Как описано в разделе Основные сведения о маркерах OAuth, процесс аутентификации OAuth выполняется в три шага. Эти шаги описаны в следующих разделах.
Получение маркера запроса Авторизация маркера запроса Получение маркера доступа
Шаг 1. Получение маркера запроса
Для получения маркера запроса отправьте подписанный запрос наhttps://www.google.com/accounts/OAuthGetRequestToken. Включите в запрос следующие параметры. Обратите внимание на то, что необязательным параметром является только oauth_version; все остальные параметры обязательны.
Параметр |
Описание |
oauth_consumer_key |
Это значение определяет связанный с приложением домен, который должен совпадать с доменом, зарегистрированным в Google. |
oauth_signature_method |
Это значение указывает алгоритм формирования подписи, использовавшийся для подписания запроса. Поддерживаемыми значениями для этого параметра являются RSA-SHA1 и HMAC-SHA1. |
oauth_signature |
Это значение указывает строку (подпись), созданную с использованием вышеозначенного метода подписания. Дополнительные сведения см. в разделе Подписание запросов, использующих аутентификацию OAuth. |
oauth_timestamp |
Это значение указывает время, когда был отправлен запрос. Временная метка должна быть представлена количеством секунд после 1 января 1970 г., 00:00:00 GMT. |
oauth_nonce |
Это значение является случайным 64-разрядным беззнаковым числом, представленным строкой ASCII-символов в десятичном формате. Пара текущее время/временная метка должна всегда быть уникальна для предотвращения взлома путем замещения оригинала. |
oauth_version |
Это значение указывает версию OAuth, которую должен использовать Google для обработки запроса. Значение по умолчанию – 1.0. Если в запрос включен этот параметр, он должен иметь значение 1.0. |
scope |
Это значение указывает службу, к которой вы пытаетесь получить доступ для совершения прошедших аутентификацию запросов. Этот параметр используется только для Google и не определен в стандарте OAuth. Установите значение этого параметра равнымhttp://gdata.youtube.com. |
Предусмотрены три способа задания этих параметров.
Включение параметров в заголовок Authorization запросов GET или POST. Пример запроса, приведенный ниже, показывает, как задать эти значения в заголовке Authorization. Обратите внимание, что параметр scope не может быть задан в заголовке. Параметр "scope" можно указывать в виде параметра URL запроса или в теле запроса POST.
Указание параметров в теле запроса POST. Если выбран этот метод, необходимо задать значение заголовка запросаContent-Type равным application/x-www-form-urlencoded.
Включение параметров в URL запроса GET.
Дополнительные сведения см. в разделе Спецификация OAuth (раздел 5.2).
Пример запроса
В примере запрашивается маркер, необходимый для доступа к аккаунтам Календаря и Picasa пользователя.
POST /accounts/OAuthGetRequestToken HTTP/1.1
Host: https://www.google.com
Content-Type: application/x-www-form-urlencoded
Authorization: OAuth
oauth_consumer_key="example.com",
oauth_signature_method="RSA-SHA1",
oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
oauth_timestamp="137131200",
oauth_nonce="4572616e48616d6d65724c61686176",
oauth_version="1.0"
scope=http://gdata.youtube.com
Об ответе
Если запрос выполнен успешно, то Google отвечает сообщением HTTP 200, которое содержит маркер запроса и секретную часть маркера. Приложению необходимо выполнить синтаксический анализ, выделив маркер из ответа.
Если не удалось успешно обработать запрос, то Google возвращает сообщение HTTP 400 (Запрос отклонен). Такой ответ может указывать на то, что запрос имеет неверный формат, в нем содержатся неподдерживаемые параметры, отсутствуют необходимые параметры, использован неподдерживаемый метод подписи или имеются какие-то другие ошибки в формате или содержании. Этот ответ может также указывать на то, что у Google есть основания полагать, что запрашивающий – злоумышленник.
В следующем примере показан успешный ответ на запрос маркера.
oauth_token=ab3cd9j4ks73hf7g&oauth_token_secret=ZXhhbXBsZS5jb20
Шаг 2. Авторизация маркера запроса
После получения маркера запроса перенаправьте пользователя наhttps://www.google.com/accounts/OAuthAuthorizeToken. Добавьте в URL следующие параметры запроса.
Параметр |
Описание |
oauth_token |
Этот обязательный параметр задает полученный от Google маркер запроса. |
oauth_callback |
Этот необязательный параметр задает URL, на который будет перенаправлен пользователь после того, как приложение получит доступ к своему аккаунту на YouTube. URL может включать параметры запроса. Если не задан URL обратного вызова, то Google выводит страницу подтверждения авторизации маркера пользователя. Обратите внимание, что указание URL обратного вызова дает возможность автоматически перенаправить пользователя к приложению после входа в систему. |
Пример запроса
В следующем примере показан запрос для авторизации маркера.
https://www.google.com/accounts/OAuthAuthorizeToken?oauth_token=ab3cd9j4ks73hf7g&oauth_callback=http%3A%2F%2Fwww.example.com
Об ответе
Если Google примет этот запрос, то пользователь будет перенаправлен на страницу входа в YouTube. После входа пользователя служба аутентификации отображает страницу, информирующую его о том, что приложению требуется доступ к аккаунту пользователя на YouTube. На странице запрашивается подтверждение пользователя на доступ приложения к его аккаунту.
Если пользователь предоставил приложению доступ, то он перенаправляется на URL обратного вызова, заданный в запросе на авторизацию маркера. Если пользователь запретил доступ, то он будет перенаправлен на страницу, содержащую ссылки на сайт и на YouTube.
Следующий URL показывает формат перенаправления на URL обратного вызова с авторизованным маркером запроса. Авторизованный маркер запроса имеет то же значение, что и неавторизованный маркер, отправленный в запросе. Значением является текстовая строка длиной до 256 байт.
http://www.example.com/ytapi.html?oauth_token=CKF50YzIHxCT85KMAg
Шаг 3. Обмен маркера запроса на маркер доступа
После авторизации маркера запроса пользователя приложение может обменять этот маркер на маркер доступа. Маркер доступа позволяет отправлять удостоверенные запросы к API YouTube от имени конкретного пользователя. Время действия маркеров доступа не ограничено, они работают как маркеры сеанса AuthSub.
Чтобы заменить авторизованный маркер запроса маркером доступа, отправьте подписанный запрос HTTP наhttps://www.google.com/accounts/OAuthGetAccessToken со следующими параметрами запроса. Обратите внимание, что все эти параметры, кроме oauth_token, также используются (в том же контексте) в запросах для получения маркера запроса. Единственным необязательным параметром является oauth_version, все остальные обязательны.
Параметр |
Описание |
oauth_consumer_key |
Это значение определяет связанный с приложением домен, который должен совпадать с доменом, зарегистрированным в Google. Это значение не должно отличаться от значения, представленного в запросе на получение маркера запроса. |
oauth_token |
Оно определяет авторизованный маркер запроса. |
oauth_signature_method |
Это значение указывает алгоритм формирования подписи, использовавшийся для подписания запроса. Поддерживаемыми значениями для этого параметра являются RSA-SHA1 и HMAC-SHA1. |
oauth_signature |
Это значение указывает строку (подпись), созданную с использованием вышеозначенного метода подписания. |
oauth_timestamp |
Это значение указывает время, когда был отправлен запрос. Временная метка должна быть представлена количеством секунд после 1 января 1970 г., 00:00:00 GMT. |
oauth_nonce |
Это значение является случайным 64-разрядным беззнаковым числом, представленным строкой ASCII-символов в десятичном формате. Пара текущее время/временная метка должна всегда быть уникальна для предотвращения взлома путем замещения оригинала. |
oauth_version |
Это значение указывает версию OAuth, которую должен использовать Google для обработки запроса. Значение по умолчанию – 1.0. Если в запрос включен этот параметр, он должен иметь значение 1.0. |
Задать эти три параметра приложение может теми же тремя способами, которые применялись для указания параметров при получении маркера запроса.
Пример запроса
Следующий пример меняет маркер запроса на маркер доступа.
POST /accounts/OAuthGetAccessToken HTTP/1.1
Host: https://www.google.com
Content-Type: application/x-www-form-urlencoded
Authorization: OAuth
oauth_consumer_key="example.com",
oauth_token="CKF50YzIHxCT85KMAg",
oauth_signature_method="RSA-SHA1",
oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
oauth_timestamp="137131200",
oauth_nonce="4572616e48616d6d65724c61686176",
oauth_version="1.0"
Об ответе
Если запрос маркера доступа обработан успешно, то Google отвечает сообщением HTTP 200, которое содержит маркер доступа и секретную часть маркера. В следующем примере показан образец ответа, содержащего маркер доступа OAuth.
oauth_token=ab3cd9j4ks73hf7g&oauth_token_secret=ZXhhbXBsZS5jb20
Google может отклонить запрос маркера, если он имеет неверный формат или есть основания полагать, что запрашивающий – злоумышленник. В этом случае возвращается код ответа HTTP 400 ("Запрос отклонен").
Подписание запросов, использующих аутентификацию OAuth
Все запросы на получение или использование маркера OAuth должны быть подписаны. Это требование относится к вызовам для получения маркера запроса и маркера доступа, а также ко всем запросам к API данных YouTube, которые требуют аутентификации, например запросам на добавление видео или его рейтинга.
Следующие шаги описывают процедуру формирования подписи запросов, в которых используется аутентификация OAuth.
Параметр oauth_signature_method служит для указания алгоритма подписи запроса. Google поддерживает алгоритмы формирования подписи RSA-SHA1 и HMAC-SHA1.
Создайте подпись base string, состоящую из трех элементов запроса, разделенных амперсандами (&). Обратите внимание, что все имена параметров, а также их значения должны быть закодированы согласно спецификации OAuth (раздел 5.1).
Первый элемент определяет метод HTTP-запроса (GET, POST и т. д.).
Второй элемент задает базовый URL для этого запроса, который не должен включать параметры. Например, если URL запроса равен http://gdata.youtube.com/feeds/api/videos?q=football&orderby=published&v=2, то базовый URL будет равен http://gdata.youtube.com/feeds/api/videos.
Третий элемент содержит нормализованную строку параметра и его значения. Эта строка должна включать все параметры запроса, заданные в URL запроса, а также все параметры OAuth, кроме oauth_signature. Способ нормализации строки определяется спецификацией OAuth (раздел 9.1.1).
Создайте oauth_signature с использованием указанного oauth_signature_method.
Если используется алгоритм RSA-SHA1, то сформируйте подпись с помощью закрытого ключа, соответствующего сертификату, который был добавлен на Google во время регистрации.
Если используется алгоритм HMAC-SHA1, создайте подпись с использованием значения "consumer secret", сформированного во время регистрации. Это значение отображается на странице регистрации вашего домена.
В спецификации OAuth (приложение A.5) приведен пример запроса, который может быть использован для проверки подписи HMAC-SHA1. Пример включает базовую строку подписи и ключ HMAC-SHA1, а также сформированный дайджест (подпись) HMAC-SHA1.
Примечание. Дополнительную информацию о подписи запросов OAuth см. в спецификации OAuth (раздел 9).
Схема процесса
На следующем рисунке показаны шаги аутентификации пользователя по протоколу OAuth. Подобно аутентификации AuthSub и ClientLogin аутентификация OAuth может использоваться или с прямым добавлением, или с добавлением через браузер.
На этом рисунке показаны следующие шаги.
Пользователь щелкает ссылку на вашем сайте, чтобы выполнить операцию API, требующую аутентификации.
Ваше приложение проверяет наличие сохраненного маркера доступа OAuth для этого пользователя. Если маркер доступа имеется, ваше приложение переходит к шагу 14. Если маркер доступа отсутствует, ваше приложение переходит к шагу 3.
Ваше приложение передает подписанный запрос на получение маркера запроса службе аутентификации OAuth Google.
Google в ответ на запрос выдает неавторизованный маркер запроса, который приложение извлекает из ответа путем синтаксического анализа.
Ваше приложение перенаправляет пользователя к службе аутентификации аккаунтов YouTube. URL перенаправления определяет неавторизованный маркер запроса и URL обратного вызова.
Браузер пользователя выполняет переадресацию, посылая запрос службе аутентификации аккаунтов YouTube.
Служба аутентификации аккаунтов YouTube отображает страницу Подтверждение доступа, предлагая пользователю войти в свой аккаунт YouTube и разрешить либо запретить вашему приложению выполнять операции API для этого аккаунта.
Пользователь выполняет вход и разрешает аккаунту доступ к вашему приложению.
Если пользователь разрешает доступ, YouTube снова перенаправляет пользователя на URL обратного вызова, который был включен в URL на шаге 6. Пользователь может запретить аккаунту доступ, и в этом случае YouTube выведет страницу со ссылками для перехода обратно к вашему приложению и на веб-сайт YouTube.
Браузер пользователя обрабатывает перенаправление и отправляет запрос на ваш URL обратного вызова. URL перенаправления содержит значение авторизованного маркера запроса. (Значение авторизованного маркера запроса такое же, как у неавторизованного.)
Ваше приложение отправляет службе аутентификации OAuth YouTube подписанный запрос обмена маркера запроса на маркер доступа.
YouTube в ответ на запрос выдает маркер доступа.
Ваше приложение выполняет синтаксический разбор маркера доступа из ответа и сохраняет значение маркера, которое связано с конкретным пользователем. В будущем приложение будет использовать этот маркер доступа при отправке прошедших аутентификацию запросов для того же пользователя.
Приложение отправляет запрос API с использованием маркера доступа для аутентификации запроса.
Социальные сети широко используются людьми по всему миру. Web-сайты, подобные Facebook и Twitter, становятся все более и более популярными, но, к сожалению, учитывая сложности взаимодействия между ними, эти Web-сайты похожи на изолированные острова, разбросанные в океане. Несмотря на то, что многие из этих сайтов предоставляют API для представления некоторых своих данных, агрегировать много таких Web-сайтов в для mashup-сайте непросто. Например, необходимо управлять пользовательскими учетными записями для различных Web-сайтов, поэтому для облегчения работы пользователей требуется в некотором роде единая регистрация (single sign-on). Пользователи не всегда охотно предоставляют свои пароли стороннему Web-сайту.
В данной серии статей описывается открытый протокол OAuth, позволяющий пользователям предоставлять свои частные данные различным Web-сайтам вместе со своими полномочиями, но показывать эти данные только исходному Web-сайту, где они хранятся. В этой первой статье дается введение в OAuth с примером настольного приложения, позволяющего пользователям обновлять свой Twitter-статус, используя OAuth. Во второй части данной серии статей будет рассказано, как разработать более практичное Web-приложение Twitter-клиента, использующее OAuth. В последней статье речь пойдет о том, как развернуть разработанное во второй части Web-приложение в среде Google App Engine (GAE).
Для работы с данной статьей необходимо иметь опыт Web-разработки с использованием технологии Java™ Servlet/JSP. В качестве Web-контейнера был выбран Apache Tomcat, а вся разработка проводится в среде Eclipse. Однако вы сможете адаптировать данную информацию для предпочитаемых вами инструментальных средств. Отметим, что для компилирования исходного кода необходим JDK 5+.
Введение в oAuth
Протокол OAuth позволяет пользователям предоставлять частные ресурсы, хранящиеся на Web-сайте, другим сайтам, не показывая информацию о полномочиях (например, имя пользователя и пароль) Web-сайтам, отличным от сайта, хранящего данные. Web-сайт, применяющий OAuth в качестве одного из своих протоколов аутентификации, улучшает защищенность и секретность для пользователей. В OAuth есть три ключевых игрока: пользователь (user), потребитель (consumer) и поставщик сервиса (service provider) (см. врезку). Типичный сценарий работы OAuth можно описать следующим образом (полное описание OAuth приведено в спецификации "OAuth Core 1.0a" и "Руководстве для начинающих", ссылки на которые приведены в разделе Ресурсы):
Ключевые игроки oAuth
Пользователь: лицо, имеющее учетную запись у поставщика сервиса.
Потребитель: Web-сайт или приложение, использующее OAuth для доступа к поставщику сервиса.
Поставщик сервиса: Web-приложение, предоставляющее доступ через протокол OAuth.
Web-сайт PHOTO (поставщик сервиса) хранит некоторые частные фотографии пользователя (пользователь). Но этот сайт не предоставляет сервис печати. Поэтому, когда пользователь захочет распечатать свои фотографии, он может перейти на Web-сайт PRINTER (потребитель), предоставляющий сервис печати. Но Web-сайт PRINTER не имеет фотографий пользователя. Следовательно, должен существовать поток данных от сайта PHOTO к PRINTER. Вот здесь может помочь OAuth. Если сайт PHOTO позволяет выполнять аутентификацию с использованием OAuth, то при желании пользователя распечатать фотографии (хранящиеся на сайте PHOTO) сайт PRINTER направит его на сайт PHOTO для предоставления доступа к фотографиям. Здесь пользователь может быть аутентифицирован с использованием обычной пары имя-пароль. После аутентификации на сайте PHOTO пользователю может быть выдан запрос на разрешение доступа сайту PRINTER для чтения фотографий. (Это разрешение может быть установлено заранее, и этот шаг может пропускаться.) После этого пользователь будет направлен на сайт PRINTER. Теперь сайт PRINTER имеет разрешение на чтение пользовательских фотографий с сайта PHOTO и сможет распечатать их по требованию пользователя. На всех шагах сайт PRINTER не имеет информации о полномочиях пользователя, зарегистрированной на сайте PHOTO. Таким образом, частные данные пользователя защищаются лучше. На рисунке 1 продемонстрирован описанный выше процесс в виде упрощенной схемы последовательности действий.
Рисунок 1. OAuth-взаимодействия между пользователем, потребителем и поставщиком сервиса
(Подробное описание данного рисунка.)
Спецификация OAuth Core 1.0 была завершена в конце 2007 года. На время написания данной статьи последней версией является 1.0a, в которой исправлена проблема системы защиты, связанная с атаками фиксирования сессии в потоке подтверждения маркера запроса OAuth. Сегодня OAuth принят самыми крупными игроками на интернет-рынке, такими как Google и Yahoo.
Разработка настольного клиентского приложения для Twitter
Twitter, один из наиболее успешных примеров социальных сетей, привлек большое количество пользователей. Не только люди, но также и компании используют Twitter для быстрого обновления своего статуса. Например, IBM developerWorks использует Twitter для информирования людей о новостях сайта. Не удивительно, что Twitter поддерживает OAuth, поэтому я рассмотрю его в данной статье. Сначала я расскажу, как разработать простое клиентское приложение для Twitter, использующее OAuth. Если у вас еще нет учетной записи на Twitter, зарегистрируйтесь на сайте http://twitter.com и продолжите работу с данной статьей.
Регистрация настольного приложения на Twitter: MyTtDesktopClient
В настоящее время Twitter поддерживает два типа аутентификации: Basic Auth и OAuth. Хотя Basic Auth прост в использовании, имена пользователей и пароли не защищены во время передачи по протоколу HTTP, поэтому я не буду рассматривать его в данной статье. Чтобы иметь возможность использовать OAuth с Twitter, необходимо сначала зарегистрировать приложение наhttp://twitter.com/oauth_clients. Я зарегистрировал свое приложение со следующей информацией.
Application Name (название приложения): MyTtDesktopClient.
Description (описание): настольное клиентское приложение для Twitter, использующее OAuth.
Application Website (Web-сайт приложения): введите домашнюю страницу вашего приложения.
Application Type (тип приложения): Client – мы собираемся разработать настольное клиентское приложение.
Default Access type (тип доступа по умолчанию): Read & Write (чтение и запись) – мы хотим предоставлять пользователям доступ по записи.
Use Twitter for login (использовать ли Twitter для регистрации): No (нет) – мы не собираемся использовать Twitter для аутентификации.
Отмечу, что необходимо выбрать тип приложения Client, а не Browser. (Во второй части данной серии статей я рассмотрю пример Web-приложения.) Также не забудьте указать доступ Read & Write, в противном случае ваше приложение сможет только читать данные с Twitter.
После успешной регистрации вы получите ключ потребителя (consumer key), секретную информацию (consumer secret) и три URL-адреса (URL маркера запроса (request token), URL маркера доступа (access token) и URL авторизации). Теперь все готово для реальной работы.
Разработка и тестирование MyTtDesktopClient
Я буду использовать Twitter4J, Java-библиотеку для TwitterAPI с открытыми исходными кодами (см. раздел Ресурсы). Twitter4J охватывает много функций, поэтому мы можем сосредоточиться на логике, не касаясь низкоуровневых API-вызовов к Twitter. Дополнительным преимуществом Twitter4J является то, что она поддерживает OAuth, что значительно облегчает нашу работу. Давайте начнем с метода updateStatus(String status) класса myttdesktopclient.OAuthTwitterClient, приведенного в листинге 1. Исходный код доступен по ссылке, приведенной в разделе Загрузка. Не забудьте поместить ваши ключ и секретную информацию (эти параметры были предоставлены вам после регистрации клиентского приложения на Twitter) вOAuthTwitterClient.java.
Листинг 1. Метод updateStatus(String status) класса myttdesktopclient.OAuthTwitterClient
public void updateStatus(String status) throws TwitterException, IOException {
Twitter twitter = new Twitter();
twitter.setOAuthConsumer(consumerKey, consumerSecret);
RequestToken requestToken = twitter.getOAuthRequestToken();
AccessToken accessToken = null;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (accessToken == null) {
System.out.println("Open the following URL and grant access to
your account:");
System.out.println(requestToken.getAuthorizationURL());
// Скопируйте URL авторизации в ваш браузер
// http://twitter.com/oauth/authorize?oauth_token=xxxxxxxxxxxxxxxxxxxxxxxx
// Войдите в Twitter, предоставьте доступ
// Получите PIN, например 738805
// Отметим, что вы можете использовать HttpClient для этого
System.out.println("Copy the PIN displayed in your browser, then
hit ENTER: ");
String pin = br.readLine();
accessToken = twitter.getOAuthAccessToken(requestToken, pin);
}
// Запомнить маркер запроса для последующей ссылки
int userId = twitter.verifyCredentials().getId();
storeAccessToken(userId, accessToken);
Status twitterStatus = twitter.updateStatus(status);
System.out.println("Successfully updated the status for user with ID "
+ userId + " to [" + twitterStatus.getText() + "].");
}
Метод updateStatus(String status) сначала создает объект Twitter, затем устанавливает ключ и секретную информацию. После этого из Twitter извлекается маркер запроса. Затем метод выводит URL для авторизации пользователя с целью предоставления доступа к данным, хранящимся на Twitter. Теперь скопируйте и поместите URL-адрес, выданный кодом, в браузер и нажмите клавишуEnter; появится запрос авторизации от MyTtDesktopClient (см. рисунок 2). Отмечу, что мы собираемся авторизоваться на сайте Twitter, и наше настольное приложение не знает о том, как это происходит.
Рисунок 2. Предоставление доступа к MyTtDesktopClient
При авторизации запроса Twitter возвращает PIN, как показано на рисунке 3.
Рисунок 3. Twitter возвращает PIN в MyTtDesktopClient
Теперь скопируйте и поместите этот PIN в окно терминала, в котором работает ваш код, и нажмите клавишу Enter. Вы должны увидеть информацию, аналогичную приведенной в листинге 2. Как видно из листинга 1, настольное клиентское приложение интересуется только выходными данными процесса авторизации. Если он успешен (в данном случае Twitter возвращает PIN), предоставляется маркер доступа, благодаря которому клиентское приложение может обновить свой Twitter-статус от имени пользователя. Это ключевой момент OAuth. Секретные данные о пользователе (имя и пароль) приложению MyTtDesktopClient. не предоставляются. Если вы зарегистрируетесь в Twitter, то увидите последнее обновление "Studying OAuth" (см. рисунок 4). Отмечу, что сообщение ниже последнего статуса указывает на то, что он был обновлен приложением MyTtDesktopClient.
Листинг 2. Выходная информация MyTtDesktopClient
Open the following URL and grant access to your account:
http://twitter.com/oauth/authorize?oauth_token=matzsp0Q8k5GfhX2DQoQaHEipBH3g3ieqzPg5QyYek
Copy the PIN displayed in your browser, then hit ENTER:
1944867
Storing access token for 23741179...Done
Successfully updated the status for user with ID 23741179 to [Studying OAuth].
Рисунок 4. Статус Twitter обновлен приложением MyTtDesktopClient
Отмечу, что в листинге 1 перед обновлением статуса Twitter маркер доступа сохраняется путем вызова метода storeAccessToken. Причина этого заключается в том, что в настоящее время маркер доступа от Twitter не имеет даты истечения срока годности. Следовательно, нет необходимости получать доступ снова при повторном использовании нашего клиентского приложения пользователем. Это улучшает процесс работы с пользователем. В данном примере маркер доступа сохраняется в обычном текстовом файле (plain text) для пользователя. Его можно извлекать для последующего использования. Взгляните на методыstoreAccessToken и loadAccessToken в исходном коде (см. ссылку в разделе Загрузка). Новый метод обновленияupdateStatus(int userId, String status) использует сохраненный маркер доступа (см. листинг 3). В методе mainзакомментируйте строку tt.updateStatus("Studying OAuth"); и удалите символы комментирования в строкеtt.updateStatus(23741179, "Studying OAuth again");. Замените число 23741179 вашим собственным Twitter ID. Повторно скомпилируйте и запустите код. Статус Twitter обновится без процесса авторизации.
Листинг 3. Метод updateStatus(int userId, String status) класса myttdesktopclient.OAuthTwitterClient
public void updateStatus(int userId, String status)
throws TwitterException, IOException {
Twitter twitter = new Twitter();
twitter.setOAuthConsumer(consumerKey, consumerSecret);
AccessToken accessToken = loadAccessToken(userId);
twitter.setOAuthAccessToken(accessToken);
Status twitterStatus = twitter.updateStatus(status);
System.out.println("Successfully updated the status for user with ID "
+ userId + " to [" + twitterStatus.getText() + "].");
}
Резюме
В этой первой статье серии я представил протокол OAuth. На примере приложения MyTtDesktopClient мы познакомились с основными принципами использования OAuth. Особенностью здесь является то, что данные о пользовательских полномочиях, хранящиеся у поставщика сервисов (имя пользователя и пароль в Twitter), не предоставляются потребителю - приложениюMyTtDesktopClient. Во второй части данной серии я продемонстрирую, как разработать более практичное Web-приложение, позволяющее пользователям взаимодействовать с Twitter по протоколу OAuth.
OAuth — популярный протокол, который позволяет социальным сервисам интегрироваться между собой и дает безопасный способ обмена персональной информацией. OAuth может связать между собой 2 сервиса, каждый из которых имеет свою пользовательскую базу — именно их я в данном случае называю "социальными". Когда начинаешь работать с OAuth, первое ощущение — что протокол весьма сложен и избыточен. В этой статье я попытаюсь объяснить основы OAuth человеческим языком.
Пример кросс-авторизации
Вернемся в 2005-й год и представим, что мы пишем социальную сеть. В ней имеется форма импорта контактов из адресной книги GMail. Что нужно для доступа к контактам GMail? Конечно, логин и пароль от ящика. Но если мы попросим ввести их на нашем сайте, пользователь заподозрит неладное. Где гарантия, что мы не сохраняем на сервере введенные пароли? Поэтому нам хочется, чтобы пароль вводился только на сайте GMail, и после этого доступ к контактам через API GMail предоставлялся нашей социальной сети (возможно, на время).
|
Это выглядит следующим образом: форма состоит из единственной кнопки — «Импортировать контакты». После нажатия на нее пользователя временно редиректят на GMail, где он вводит свой логин и пароль (а если уже авторизован, то ничего не вводит). Далее пользователя возвращают обратно на наш сайт, где скрипт уже получает возможность скачать контакты через внутренний API GMail. |
Договоримся о терминах.
Consumer: потребитель; скрипт обработки формы импорта контактов в социальной сети.
Service Provider: поставщик данных; GMail, содержащий в себе данные адресной книги, интересные для Consumer-а.
User: пользователь, имеющий аккаунт как у Consumer-а, так и у Service Provider-а.
Protected Resource: личные данные; контакты из адресной книги на GMail (т.е. ресурсы Service Provider-а).
Provider API: API GMail, позволяющий любому скрипту получить контакты из адресной книги GMail.
|
Сейчас я прошу вас закрыть листом бумаги верхнюю часть экрана и в качестве упражнения ответить на вопросы: кто такой Service Provider? что такое Protected Resource? кто такой Consumer и чем он отличается от User-а? где располагается API? Далее в статье мы свободно оперируем этими терминами. Если вы сейчас недостаточно хорошо в них ориентируетесь, могут быть проблемы с пониманием. |
Задача OAuth — сделать так, чтобы User имел возможность работать на сервисе Consumer (в соцсети) с защищенными данными Service Provider-а (GMail), вводя пароль к этим данным исключительно на Service Provider-e и оставаясь при этом на сайте Consumer-а. Не так уж и сложно, верно?
Чем oAuth отличается от OpenId?
OAuth часто называют «протоколом для роботов», в отличие от OpenID - «протокола для пользователей». Не путайте их!
OpenID — протокол для ускоренной регистрации. OpenID позволяет пользователю без ввода пароля получить аккаунт на каком-либо сервисе, если он уже зарегистрирован где-то еще в интернете. (И потом можно без ввода пароля входить на сервис, будучи авторизованным "где-то".) Например, если у вас есть аккаунт на Яндексе, вы сможете "входить" с его помощью на любой сервис, поддерживающий OpenID-авторизацию.
OAuth — протокол для авторизованного доступа к стороннему API. OAuth позволяет скрипту Consumer-а получить ограниченный API-доступ к данным стороннего Service Provider-а, если User дает добро. Т.е. это средство для доступа к API.
|
Милицейская аналогия Представьте, что вы — сотрудник Уголовного розыска, ищущий концы в деле о краже WebMoney за 1973-й год. Договоримся о терминах:
Как видите, OpenID и OAuth — разные вещи. OpenID позволяет вам прямо на месте получить доступ к некоторым ресурсам. OAuth обеспечивает получение части информации с удаленного сервиса через API. |
План этой статьи
Прежде чем перейти к основной части, давайте посмотрим, как именно мы будем двигаться.
Рассмотрим проблемы, которые возникают при "ручной" реализации кросс-авторизации.
Поговорим о том, что такое "приложение" и кто такой Consumer.
Коснемся основ криптографии.
Обозначим демо-приложение, которое мы будем писать в этой статье.
Определимся с тестовым сервером OAuth, на котором будем экспериментировать.
Пройдем по всем шагам протокола OAuth и приведем исходники скрипта.
Об изобретении велосипедов
Хороший способ понять что-то — сделать это самому, наступив попутно на все разложенные грабли. Сейчас мы будем изобретать велосипед: попробуем представить, как бы мы решали задачу взаимодействия Consumer-а и Service Provider-а без всяких стандартизированных протоколов.
Во-первых, напишем саму форму импорта контактов с GMail:
скопировать код в буфер обмена Листинг 1: Велоформа импорта контактов |
<form action="http://gmail.com/auth.php?retpath=http://oursocialnetwork.ru/import.php" method="get"> <input type="submit" value="Загрузить адресную книгу" /> </form> |
Далее попросим разработчиков GMail сделать так, чтобы при переходе пользователя по URI /auth.php ему бы выдавалась форма авторизации (в нашем веломире GMail написан на PHP). После успешного ввода пароля пользователь должен редиректиться на сайт, чей URL указан в параметре retpath. Также дополнительно в URL должен передаваться некоторый секретный ключ, который уже можно использовать для доступа к API GMail.
Итак, после ввода пароля пользователь будет возвращаться к нам на сайт по следующему адресу:
скопировать код в буфер обмена Листинг 2: Велоадрес возврата с велоключом |
http://oursocialnetwork.ru/import.php?secret=Y49xdN0Zo2B5v0RR |
А мы из скрипта /import.php обратимся к API GMail, передадим в него ключ Y49xdN0Zo2B5v0RR и загрузим контакты:
скопировать код в буфер обмена Листинг 3: Запуск метода велоAPI |
$contacts = $gmailApi->getContacts($_GET['secret']); |
Ну что же, давайте теперь считать грабли (потому что шишки считать будет уже поздно).
Грабли первые: подмена адреса возврата retpath
Ну конечно же, вы догадались, что злоумышленник на своем сайте первым делом разместит ссылку
скопировать код в буфер обмена Листинг 4: Ссылка на сайте злоумышленника |
http://gmail.com/auth.php?retpath=http://hackersite.ru/save.php |
и заставит вас на нее кликнуть. В результате он получит секретный ключ, который вернул GMail, а значит, и ваши контакты:
скопировать код в буфер обмена Листинг 5: Велосекрет в адресе возврата |
http://hackersite.ru/save.php?secret=Y49xdN0Zo2B5v0RR |
Грабли вторые: "подслушивание" секретного ключа
Предположим, мы как-то защитили retpath, и он теперь может указывать только на наш сайт. Но проблема с параметром secret остается.
скопировать код в буфер обмена Листинг 6: Велоадрес возврата с велоключом |
http://oursocialnetwork.ru/import.php?secret=Y49xdN0Zo2B5v0RR |
Secret можно подсмотреть из-за спины или перехватить методом прослушивания WiFi-трафика. Или на вашем сайте когда-нибудь найдется XSS-уязвимость, позволяющая "утянуть" секретный ключ. Имея значение secret, злоумышленник сможет прочитать вашу адресную книгу. Значит, нужно обезопасить secret от перехвата (в идеале — вообще его не передавать через URL).
|
Нужно помнить, что секретный ключ передается не только в URL, но еще и при вызове API-методов. Там тоже возможен перехват. Конечно, использование SSL здесь помогает. |
Грабли третьи: слишком много редиректов
Если для каждого вызова API требуется разный secret, то нам придется организовывать столько редиректов на сайт Service Provider-а, сколько у нас вызовов. При интенсивном использовании API это работает очень медленно, да и неудобно порядком...
Грабли четвертые: плохая идентификация Consumer-а
GMail, конечно, хочет знать, кто пользуется его API. Разрешить доступ одним сайтам и запретить — другим... Значит, при формировании запроса в форме импорта контактов Consumer (сайт) должен "представляться" Service Provider-у (GMail-у). В нашем случае эту функцию частично выполняет retpath (имя сайта в нем), но данный способ не универсален, т.к. механизм "представления" должен быть задействован еще и при вызове API-методов.
Фундамент oAuth
Примечательно, что "подводных граблей" осталось еще много. Я не буду их здесь описывать, потому что эти грабли лежат в Марианской впадине (глубоко, 10920 м). На описание уязвимостей пришлось бы потратить с десяток страниц. Так что я сразу перейду к описанию OAuth, где все проблемы уже решены.
|
Есть замечательный цикл статей про OAuth: Beginner's Guide to OAuth (на английском; от автора с говорящим прозвищем hueniverse). Его изучение отнимет у вас приблизительно 4 часа, если вы до этого момента совершенно не знакомы с темой. |
Приложение = Consumer + доступ к API
При работе с OAuth важно, что термин Consumer не ограничивается смыслом "сайт". Consumer — это некоторое приложение, а уж где оно размещено, не так важно. Примеры Consumer-ов из реальной жизни:
Скрипт формы импорта контактов из GMail (см. пример выше).
Приложение для iPhone, позволяющее писать сообщения в Twitter.
Прямоугольный виджет на вашем сайте, в котором отображаются последние сообщения чата и есть возможность написать новое.
Но из одного 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" с гарантированной защитой от фальсификации злоумышленником.
Consumer добавляет цифровую подпись к сообщению, в общем виде —
$transfer = $message . "-" . md5($message . $sharedSecret);
// $transfer = "Мой телефон 1234567" . "-" . md5("Мой телефон 1234567" . "529AeGWg")
Service Provider принимает данные, разбивает их обратно на 2 части — $message и $signature — и проделывает точно такую же операцию:
$signatureToMatch = md5($message . $sharedSecret);
// $signatureToMatch = md5("Мой телефон 1234567" . "529AeGWg");
Дальше остается только сравнить получившееся значение $signatureToMatch с тем, что было в полученных данных $signature и рапортовать о подделке, если значения не совпали.
|
Итак, чтобы сформировать MD5-подпись, обязательно знать Shared Secret. (Кстати, кроме MD5 есть и другие алгоритмы необратимого хэширования.) Злоумышленник не знает Shared Secret, поэтому и подпись он подделать не может. |
Демонстрация работы oAuth на примере простого приложения
Чтобы "вживую пощупать" OAuth, нам потребуются две вещи:
Скрипт, реализующий клиентскую часть протокола. Я написал как раз такой небольшой PHP-скрипт (ссылка на zip-архив). Это виджет, который можно вставлять на PHP-сайты.
Тестовый сервер OAuth, на котором мы сможем экспериментировать. Для этой цели удобно использовать РуТвит: там есть страница http://rutvit.ru/apps/new, которая позволяет добавить тестовое приложение за 30 секунд. (Кстати, URL возврата в форме можно не указывать — мы все равно передаем его из тестового скрипта.)
Глядя на код демо-скрипта и читая пояснения ниже в статье, можно разобраться с деталями протокола.
Вы можете вставить данный виджет на любой PHP-сайт, просто скопировав его код и подправив верстку. Выводятся все твиты с сервиса РуТвит, помеченные указанным хэш-тэгом, а также имеется возможность добавлять новые твиты (тут-то как раз и задействуется OAuth). Виджет использует API и OAuth-авторизацию РуТвита, которые, кстати говоря, совпадают со стандартом API Twitter-а.
|
В настоящий момент для работы с OAuth в PHP есть только одна сколь-нибудь универсальная и библиотека: OAuth.php by Andy Smith. У нее два недостатка: она написана грязно, и она не обновлялась уже больше года. Ссылки на другие библиотеки приведены на сайте OAuth, однако эти инструменты либо требуют установки PHP extension, либо еще слишком сыры, либо же имеют обширные внешние зависимости от других библиотек (хотя черновик библиотеки для Zend Framework выглядит очень перспективно). Так что, как говорится, "мышки плакали, кололись, но продолжали есть кактус" — будем пользоваться OAuth.php. |
Вы можете запустить этот скрипт на своем тестовом сервере. Для этого нужно выполнить три действия:
Скачайте код скрипта и разверните его в любую удобную директорию на веб-сервере.
Зарегистрируйте новое тестовое приложение на OAuth-сервере.
После регистрации приложения замените параметры OA_CONSUMER_KEY и OA_CONSUMER_SECRET в скрипте на значения, полученные от сервера.
|
Скрипт специально написан без ООП и максимально «в лоб». Преследовались две цели: а) добиться краткости и понятности кода, б) сделать код идущим параллельно линии повествования в статье (отсюда этот конечный автомат и switch ... case). Да, и еще одно. Файл OAuth.php — не самописный, это библиотека от Andy Smith в неизменном виде (наслаждайтесь). |
Регистрация приложения и его параметры
Поговорим о том, откуда появляются приложения и как Service Provider о них узнает. Все достаточно просто: Service Provider имеет специальную форму регистрации приложений, которой может воспользоваться любой желающий. Вот пример такой формы:
После регистрации приложения вам выдается 5 параметров, которые требуются для работы с OAuth. Вот как они могут выглядеть:
Здесь Consumer key и Consumer secret — это своеобразные "логин + пароль" вашего приложения (помните выше разговор о токенах? это как раз один из них). Напомню, что Consumer secret — это Shared Secret, известный только отправителю и получателю, но никому больше. Остальные 3 значения задают служебные URL, смысл которых мы сейчас рассмотрим.
скопировать код в буфер обмена Листинг 7: Параметры OAuth и определение переменных |
<?php require_once "OAuth.php";
// Разные параметры. define("ENCODING", "windows-1251"); // Кодировка сайта. Если у вас UTF-8, то вы молодец! define("TAG", "support"); // Тэг, по которому производится фильтрация твитов.
// Параметры OAuth. Запомните их наизусть (особенно SECRET). define("OA_CONSUMER_KEY", "JId0zVAbQCVnqjD9OlvM"); // Параметры OAuth-доступа. define("OA_CONSUMER_SECRET", "qocMBQg1P17CBcdVsJizsNPnlGbTU4fvlGxAszmzB5"); define("OA_URL_REQ_TOK", "http://api.rutvit.ru/oauth/request_token"); define("OA_URL_AUTH_TOK", "https://api.rutvit.ru/oauth/authorize"); define("OA_URL_ACCESS_TOK", "http://api.rutvit.ru/oauth/access_token"); |
OAuth = Fetch Request Token + Redirect to Authorization + Fetch Access Token + Call API
|
...или, в переводе на великий могучий:
|
В примере с GMail мы использовали 2 вида удаленных вызовов: а) редирект через браузер; б) обращение к API изнутри скрипта.
И мы вскрыли ряд проблем с безопасностью, что наводит на мысль: вызовов должно быть больше. Так и происходит в OAuth: добавляются еще промежуточные запросы от скрипта Consumer-а к Provider-у, оперирующие токенами. Давайте их рассмотрим.
скопировать код в буфер обмена Листинг 8: Обрабатываем смену состояний через конечный автомат |
// Для работы с OAuth нам требуется 3 переменные, сохраняющие свои значения // между загрузками страниц (для простоты - храним их в сессии). session_start(); $S_MSG = &$_SESSION['msg']; $S_REQUEST_TOK = &$_SESSION['REQUEST_TOK']; $S_ACCESS_TOK = &$_SESSION['ACCESS_TOK'];
// Путь: // form_is_sent -> // fetch_request_token -> // authorize_request_token (через браузер) -> // fetch_access_token (обмен request_token на access_token) -> // send_msg (через API) // Или: // form_is_sent -> // send_msg (через API) $action = @$_GET['action']; while ($action) { switch ($action) { |
Обработка отправки формы. Это не часть OAuth, а часть нашего приложения. Прежде чем обращаться к API Provider-а, мы должны получить от пользователя заказ-наряд на это действие. Вот пример такого "заказа":
скопировать код в буфер обмена
Листинг 9: Обработка отправки формы
// 1. Запрошена отправка формы. Определяем, с какого шага начинать:
// либо с OAuth, либо с отправки сообщения через API.
case 'form_is_sent': {
// Сохраняем сообщение в сессию, оно нам понадобится позже.
$S_MSG = $_POST['msg'];
if ($S_ACCESS_TOK && $S_ACCESS_TOK->secret) {
// Пользователь уже отправлял комментарии в текущей сессии.
$action = 'send_msg';
} else {
// Авторизация еще не проведена, запускаем процедуру OAuth.
$action = 'fetch_request_token';
}
break;
}
Fetch Request Token (внутренний запрос).
Скрипт Consumer-а обращается к Request token URL Provider-а: например, http://api.rutvit.ru/oauth/request_token. В запросе передается Consumer key — "логин приложения", а сам запрос подписывается при помощи Consumer secret — "пароля приложения", что защищает его от подделки.
В ответ Provider генерирует и возвращает "заполненный мусором" токен, который называется Request Token. Он нам еще пригодится, поэтому мы должны сохранить его где-нибудь — например, в сессионной переменной $S_REQUEST_TOK.
скопировать код в буфер обмена
Листинг 10: Fetch Request Token
// 2. Запрошено получение Request Token.
// Обращаемся к Service Provider через сокет и получаем токен.
case 'fetch_request_token': {
// Формируем запрос на получение Request Token.
$consumer = new OAuthConsumer(OA_CONSUMER_KEY, OA_CONSUMER_SECRET);
$req = OAuthRequest::from_consumer_and_token(
$consumer, NULL,
"GET", "http://api.rutvit.ru/oauth/request_token"
);
// Добавляем в запрос цифровую подпись, чтобы не подделали.
$req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(), $consumer, NULL);
// Получаем Request Token и отправляем его на авторизацию.
$parsed = OAuthUtil::parse_parameters(file_get_contents($req->to_url()));
$S_REQUEST_TOK = new OAuthToken($parsed['oauth_token'], $parsed['oauth_token_secret']);
// Переходим к следующему состоянию.
$action = 'authorize_request_token';
break;
}
Redirect to Authorization (через редирект в браузере). Теперь у нашего приложения есть уникальный Request Token. Требуется получить у пользователя разрешение на использование этого токена, т.е. попросить егоавторизовать Request Token.
Consumer редиректит браузера на специальный Authorize URL Provider-а: например, http://api.rutvit.ru/oauth/authorize. В параметрах передается Request Token Key.
Provider выводит форму авторизации для своего пользователя и, если он авторизовался, редиректит браузер назад. Куда именно? А мы указываем это в параметре oauth_callback.
скопировать код в буфер обмена
Листинг 11: Redirect to Authorization
// 3. Авторизация (подтверждение пользователем) Request Token's через редирект.
// Переадресуем браузер на Service Provider для продтверждения доступа пользователем.
// При возврате обратно в GET-параметрах будет action=fetch_access_token.
case 'authorize_request_token': {
// На этот URL вернется браузер после подтверждения.
$callbackUrl = "http://{$_SERVER['HTTP_HOST']}{$_SERVER['SCRIPT_NAME']}"
. "?action=fetch_access_token";
// Передаем callback-URL в параметрах (протокол OAuth 1.0; в 1.0a - уже не так!).
$authUrl = "http://api.rutvit.ru/oauth/authorize" . "?"
. "&oauth_token={$S_REQUEST_TOK->key}"
. "&oauth_callback=" . urlencode($callbackUrl);
// Браузерный редирект.
header("Location: $authUrl");
exit();
}
Fetch Access Token (внутренний запрос). Итак, браузер вернулся в наше приложение после серии редиректов. Это значит, что авторизация на Provider-е прошла успешно, и Request Token разрешен к работе. Однако в OAuth для безопасности каждый токен имеет свое собственное, строго ограниченное назначение. Например, Request Token используется только для получения подтверждения от пользователя, и больше ни для чего. Для доступа к ресурсам нам нужно получить новый токен — Access Token — или, как говорят, "обменять Request Token на Access Token".
Consumer обращается к Access token URL — например, http://api.rutvit.ru/oauth/access_token, — и просит выдать ему Access Token взамен имеющегося у него Request Token-а. Запрос подписывается цифровой подписью на основе Request Token secret.
Provider генерирует и возвращает Access Token, заполненный "мусором". Он также помечает в своих таблицах, что для этого Access Token-а разрешен доступ к API. Наше приложение должно сохранить у себя Access Token, если оно собирается использовать API в дальнейшем.
скопировать код в буфер обмена
Листинг 12: Fetch Access Token
// 4. Обмен Request Token на Access Token и запись Access Token в сессию.
// Сюда вернулись из редиректа после подтверждения доступа пользователем.
case 'fetch_access_token': {
$consumer = new OAuthConsumer(OA_CONSUMER_KEY, OA_CONSUMER_SECRET);
$req = OAuthRequest::from_consumer_and_token(
$consumer, $S_REQUEST_TOK,
"GET", "http://api.rutvit.ru/oauth/access_token",
array() // доп. параметры
);
$req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(), $consumer, $S_REQUEST_TOK);
// Выполняем запрос и записываем Access Token в сессию.
$parsed = OAuthUtil::parse_parameters(file_get_contents($req->to_url()));
$S_ACCESS_TOK = new OAuthToken($parsed['oauth_token'], $parsed['oauth_token_secret']);
// Переход к отправке сообщения.
$action = 'send_msg';
break;
}
Call API (внутренний запрос). Ну что же, теперь у нас есть Access Token, и мы можем передавать его key при вызове методов API.
Consumer генерирует запрос к API Provider-а (например, используя POST-запрос согласно REST-идеологии). В запросе передается Access Token Key, а подписывается он при помощи Shared Secret этого токена.
Provider обрабатывает API-вызов и возвращает данные приложению.
-
скопировать код в буфер обмена
Листинг 13: Call API
// 5. Отправляем сообщение.
// Оборачиваем URL API в OAuth-контейнер.
case 'send_msg': {
$consumer = new OAuthConsumer(OA_CONSUMER_KEY, OA_CONSUMER_SECRET);
$req = OAuthRequest::from_consumer_and_token(
$consumer, $S_ACCESS_TOK,
'POST', 'http://api.rutvit.ru/statuses/update.xml',
array('status' => "#" . TAG . " " . iconv(ENCODING, "UTF-8", $S_MSG))
);
$req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(), $consumer, $S_ACCESS_TOK);
// Отправляем POST-запрос.
$h = curl_init();
curl_setopt($h, CURLOPT_URL, $req->get_normalized_http_url());
curl_setopt($h, CURLOPT_POST, true);
curl_setopt($h, CURLOPT_RETURNTRANSFER, true);
curl_setopt($h, CURLOPT_POSTFIELDS, $req->to_postdata());
$resp = curl_exec($h);
$code = curl_getinfo($h, CURLINFO_HTTP_CODE);
// При успехе - редирект обратно на страницу с виджетом.
if ($code != 200) {
e($resp);
exit();
}
header("Location: {$_SERVER['SCRIPT_NAME']}");
exit();
}
Конец скрипта: вывод виджета
Окончание скрипта должно быть понятно и без подробных разъяснений.
скопировать код в буфер обмена Листинг 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; } ?>
<style> .hiddenLink { display: none } </style>
<div style="border: 1px solid black; padding: 0.5em"> <?foreach ($TWEETS->status as $tweet) {?> <div style="margin-bottom: 6px"> <b><?e($tweet->user->screen_name)?>:</b> <?e($tweet->text_formatted, 0)?> </div> <?}?> <form method="post" action="<?e($_SERVER['SCRIPT_NAME'])?>?action=form_is_sent" style="margin: 1em 0 0 0"> <input type="text" size="30" name="msg" /> <input type="submit" value="Отправить" /> </form> </div> |
Полезные ссылки по oAuth
Официальный сайт OAuth.
OAuth.php by Andy Smith.
Реализация протокола OAuth на всевозможных языках программирования.
Beginner's Guide to OAuth: статья про OAuth на английском, очень рекомендую к прочтению.
Оригинал этой статьи на dklab.ru
См. также:
http://rutvit.ru/apps/new: регистрация приложения на OAuth-сервере РуТвита.
Полный исходный код примеров этой статьи.
Сингапурский студент открыл «новую» уязвимость в OAuth
Спустя месяц после обнаружения уязвимости Heartbleed в библиотеке OpenSSL, другой открытый протокол оказался подверженным атаке. На этот раз дыры были найдены в популярном инструменте OAuth, который используется для идентификации пользователей множеством популярных сайтов и компаний, включая Google, Facebook, Microsoft и LinkedIn. ЦП разобрался в том, насколько это опасно.
35
— редакция ЦП тестирует кнопки UpToLike
Студент из Сингапура по имени Ван Цзин обнаружил, что используя небольшую брешь в безопасности сайтов, использующих протокол OAuth для авторизации пользователей, можно получить доступ к личным данным пользователя. Он назвал этот метод взлома Covert Redirect («Тайное перенаправление»). Например, если пользователь нажмёт на фишинговую ссылку, то получит стандартное окно авторизации Facebook с просьбой разрешить подключение приложения. После авторизации доступа, пользователь будет перенаправлен на страницу злоумышленника, который таким образом сможет получить доступ к данным пользователя на Facebook.
Логотип
уязвимости, предложенный Ваном
Если пользователь согласится на предложение, личные данные перейдут к злоумышленнику, а не целевому серверу. Издание CNET уточняет, что таким образом может утечь адрес электронной почты, дата рождения, список контактов и даже контроль над аккаунтом. Вне зависимости от того, авторизует ли пользователь приложение, его перенаправит на другой сайт по выбору злоумышленника, что может представлять дополнительную угрозу.
Ван Цзин рассказывает, что поставил в известность представителей Facebook: там ему сказали, что компания «осознаёт риски, связанные с OAuth 2.0», но «ошибка не может быть исправлена в краткосрочной перспективе». Также Ван обратился в Google, LinkedIn и Microsoft. В компании Google объяснили, что осведомлены о проблеме, а в LinkedIn 13 марта 2014 года даже опубликовали целый пост об этом. В Microsoft провели расследование и заявили, что уязвимость существует на доменах третьих сторон, а не на их собственных.
Список
сайтов, которые, по мнению Ван Цзина,
могут быть подвержены уязвимости
Исправить это не так просто. Если все сторонние приложения будут строго использовать «белый список», то атаки станут невозможными. В действительности, однако, большое число сторонних приложений не делают этого по разным причинам. Это делает системы, основанные на OAuth 2.0, крайне уязвимыми.
— Ван Цзин
Издание CNET сообщает, что сотрудники LinkedIn и PayPal в разное время публиковали официальные заявления об этой или похожей уязвимости.
Демонстрация работы Covert Redirect
После того, как информация о Covert Redirect начала распространяться по сети, многие издания поспешно окрестили уязвимость «новым Heartbleed». Другие СМИ и компании поспешили успокоить взволнованных представителей IT-индустрии, утверждая, что несколько преждевременно поднимать шум по этому поводу.
Представители Symantec пишут, что некорректно называть это «уязвимостью», Covert Redirect — брешь в безопасности. В отличие от Heartbleed, который позволяет напрямую воздействовать на сервер, в случае с Covert Redirect злоумышленнику требуется использовать поддающееся атаке приложение (которых не так много), а также убедить пользователя вступить во взаимодействие. В этом посте на примере связки Facebook и приложения ESPN подробно описывается, как именно хакер может перехватить токен пользователя, изменив параметр redirect_uri. Чтобы избежать такого, разработчику нужно убрать со своего сайта открытые переадресации и занести проверенные адреса в «белый список».
Крупные издания (например, Mashable и ZDNet) утверждают, что на данный момент не имеет смысла раздувать скандал по поводу Covert Redirect. В частности, Кристина Уоррен пишет на Mashable, что Covert Redirect раскручивают «как по учебнику», используя красивый сайт и даже логотип, а в действительности это не является новым открытием и не несёт такой опасности, как Heartbleed. Об этом (или очень похожем) методесообщалось ещё в феврале 2013 года, но широкую огласку он получает только сейчас.
Специалист по безопасности Егор Хомяков объясняет, что ничего страшного в этом нет:
Для начала, это известный баг Facebook Connect, другие провайдеры не подвержены уязвимости (автор утверждает, что подвержены), потому что Connect позволяет вам подменить значения параметров response_type и redirect_uri.
Поскольку ничего нового в этом нет, и Facebook уже давно отказался исправлять это, то исправлено это и не будет. Всё что вы можете сделать, это добавить в белый список redirect_uri на вкладке Advanced в настройках.
