0,0
рейтинг
8 декабря 2009 в 17:48

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 — «протокола для пользователей». Не путайте их!
  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:
Листинг 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» с гарантированной защитой от фальсификации злоумышленником.
  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 и рапортовать о подделке, если значения не совпали.
    Итак, чтобы сформировать MD5-подпись, обязательно знать Shared Secret. (Кстати, кроме MD5 есть и другие алгоритмы необратимого хэширования.) Злоумышленник не знает Shared Secret, поэтому и подпись он подделать не может.

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


Чтобы «вживую пощупать» OAuth, нам потребуются две вещи:
  1. Скрипт, реализующий клиентскую часть протокола. Я написал как раз такой небольшой PHP-скрипт (ссылка на zip-архив). Это виджет, который можно вставлять на PHP-сайты.
  2. Тестовый сервер 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.
Вы можете запустить этот скрипт на своем тестовом сервере. Для этого нужно выполнить три действия:
  1. Скачайте код скрипта и разверните его в любую удобную директорию на веб-сервере.
  2. Зарегистрируйте новое тестовое приложение на OAuth-сервере.
  3. После регистрации приложения замените параметры 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

    … или, в переводе на великий могучий:
  1. Приложение-Consumer получает Request Token.
  2. Пользователь перенаправляется на сайт Service Provider-а и авторизует там Request Token.
  3. Приложение-Consumer обменивает Request Token на Access Token.
  4. Приложение-Consumer делает авторизованные запросы к 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) {
  1. Обработка отправки формы. Это не часть 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;
    }
  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.
    Листинг 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;
    }
  3. Redirect to Authorization (через редирект в браузере). Теперь у нашего приложения есть уникальный Request Token. Требуется получить у пользователя разрешение на использование этого токена, т.е. попросить его авторизовать Request Token.
    • Consumer редиректит браузера на специальный Authorize URL Provider-а: например, 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();
    }
  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 в дальнейшем.
    Листинг 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;
    }
  5. 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

См. также:
Дмитрий Котеров @DmitryKoterov
карма
379,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое

Комментарии (34)

  • –15
    Не вчитывался, тк знаком с OpenID.

    Но статья хорошая, спасибо!
    • +7
      А почему знакомство с OpenID повлияло на статью про OAuth? :-)
      • +1
        блин, опечатка, конечно OAuth.

        OpenID авторизацию я у себя на сайте делал, в тот же момент
  • –2
    Действительно, протокол «весьма сложен» :)
  • +1
    Класс! Как раз скоро понадобится иметь дело с OAuth, большое спасибо за статью, ушло в избранное.

    P.S.: уберите под кат чуть больше, мобильные хабраюзеры будут Вам очень признательны. Можно сразу же после «В этой статье я попытаюсь объяснить основы OAuth человеческим языком» :)
  • –2
    Начал читать и понял что такие статьи нужно розбивать по типу «Часть 1» и «Часть 2», тяжело однако в конце рабочего дня столько осилить столько полезной информации.
    • +1
      Ну, в hueniverse.com/oauth/ как раз разбито на 4 части. Рекомендую, кстати, эту статью тоже прочитать тем, кто собирается более детально работать с OAuth. Правда, она на английском и несколько другой «заточенности», чем моя.
      • +2
        Доменное имя классное :) Universe-hueniverse… (извините)
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      они его недавно обновили, работает на ура
    • 0
      Вне всякого сомнения, основные посылы статьи — выбор стабильного OpenID-провайдера и продвижение сервисов Яндекса.
  • +3
    Великолепно! Замечательная статья! Хабр еще тот ) Спасибо!
  • 0
    спасибо, хотя у меня когда пробовал oAuth с сервисами гугла хранически не получался Access Token, ошибка хеша что убей, причем если не изменяет память этот-же алгоритм на предыдущем шаге работал… не сталкивались?
  • 0
    Хорошая статья, всё понятно и доступно. Попробую внедрить oAuth в одном из следующих проектов на Grails. Тем более, насколько мне известно, для Grails есть соответствующий плагин.
  • 0
    О-я-, хорошая штука )
  • –2
    Я так понимаю oAuth похожий чемто на SAML, но немного проще. Поправте если не прав.
  • 0
    Шикарно! Появление русскоязычной статьи мне как раз в тему. Спасибо автору и dk за то что вы есть!
    • +2
      эээ… автор и dk — одно и то же лицо. :-)
      • 0
        0_о ну… все равно спасибо!
  • 0
    Вот еще есть пример довольно лаконичного использования OAuth-модуля, поставляемого в виде binary extension для PHP:

    docs.php.net/manual/ru/oauth.examples.fireeagle.php

    Там этот же конечный автомат развернут в 2 if-а. Правда, про E_NOTICE они в нем забыли. :-)
  • 0
    Остался нераскрытым один вопрос: если OAuth ни в коем случае не альтернатива OpenID, а лишь средство запросить некую служебную информацию с другого веб-сервиса, то почему же на многих сайтах используют OAuth авторизацию как полноценную замену OpenID? Кстати, на вашем форуме я наткнулся на тред с подобным вопросом: xpoint.ru/forums/programming/PHP/thread/44649.xhtml
    Предлагаю написать об этом еще одну статью ;)
    • 0
      Можно примеры таких сайтов?
      • 0
        pip.ec/
        Там можно авторизоваться, используя OAuth от гугл.
  • 0
    Спасибо за разъяснение сути ЭЦП. Честно сказать, раньше думал что это нечто страшное, сертифицируемое ФАПСИ, проприетарное и обязательно труднодоступное. Теперь смогу разрулить пару нерешенных ранее проблем.
  • +1
    вот уж действительно всё по полочкам разжёвано :)
    (кстати, не поленился приложить к экрану лист (обычной плотности для принтеров), через него просвечивает.)

    ну и вопрос из зала, немного оффтопик:
    не пробовали ли реализовать oAuth-consumer на appspot?
    с openid там известны проблемы из-за редиректов.

    Алсо,
    не совсем понятно, почему для токенов используется file_get_contents, а для api — curl.
    • 0
      Исключительно из соображений краткости примера и экономии в нем строчек, дабы статья не разрасталась так сильно (в реальной жизни, конечно, лучше все через curl делать). Через file_get_contents() в одну PHP-строчку выполняются GET-запросы. А вот POST-запросы коротко не выполнить, приходится делать через curl.
    • 0
      Разрабатывал авторизацию по OAuth для твиттера с помощью Python/Django (patched) на GAE — никаких проблем не было, urllib2 прекрасно переходит по редиректам.
  • 0
    OpenID — протокол для ускоренной регистрации.

    Неправда, OpenID служит для идентификации и ничего более. Он обеспечивает ровно две с половиной функции:
    1) уникальность ника в пределах Интернета;
    2) принадлежность ника строго определённому лицу (maskas.ru, кстати, нарушает этот принцип);
    2.5) надстройка Yadis, возможности которого частично пересекаются с OAuth.

    И именно из-за п. 2.5 OpenID многими сервисами применяется не по назначению, а для ускоренной регистрации.

    Пример: На Блогспоте можно оставить комментарий и подписаться Open-идентификатором. В ЖЖ при попытке сделать то же самое почему-то создаётся ещё и специальная урезанная учётная запись. Третьи (вроде ThrowCatch.me) вовсе игнорируют идентификацию и используют только персональные данные для автоматического заполнения анкеты при создании обычной локальной учётной записи.

    Хотя это всё оффтопик, конечно… Просто пожаловался на жизнь…
    • 0
      Только что перепроверил, да, у ЖЖ, пожалуй, самый правильный подход. Внутренняя учётная запись должна создаваться, тихо и незаметно. Как-то ведь нужно учитывать действия пользователя в системе. Просто когда-то вместо OpenID присваивался странный внутренний идентификатор вида ext_26215461, а нынче красивенько всё сделали, идентификатор светится и значок , как положено.
      • 0
        > Внутренняя учётная запись должна создаваться, тихо и незаметно
        Вот именно поэтому я и назвал OpenID «протоколом ускоренной регистрации». Нужно смотреть изнутри. Учетная запись по любому создается на стороннем сервисе, потому что ведь надо к чему-то привязывать ресурсы, создаваемые пользователем. Заметно это для человека или незаметно — уже другой вопрос, зависящий от сервиса (чаще всего — заметно).
  • 0
    3. Redirect to Authorization (через редирект в браузере).
    А без браузера можно как-то этот пункт выполнить?
    А то из приложения вызывать браузер очень не хотелось бы…
  • 0
    Дмитрий,
    какие из социальных сервисов рунета поддерживают OAuth?
    где его можно применить в рунете?
  • 0
    Отличная статья, обязательно будем использовать oAuth на своих сервисах.
  • 0
    Реализация протокола OAuth на всевозможных языках программирования.

    Что-то не вижу я там C и C++

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