Похоже, что проходят времена, когда каждый сайт требовал от пользователя зарегистрироваться. Теперь в моде аутентификация/авторизация через популярные ресурсы типа Twitter или Facebook. Не буду рассказывать об удобствах данного подхода, а перейду сразу к делу. Одним из популярных механизмов, используемых для такой аутентификации, является OAuth. О нем, а точнее, о первой его версии спецификации, применительно к Kohana v3 я сегодня расскажу.
Немного теории
Полагаю, что мало найдется желающих читать спецификацию данного протокола, поэтому попробую объяснить коротко общие идеи.
Основные понятия
Провайдер (provider
) — сервис, предоставляющий возможность аутентифицироваться пользователю. Это и есть те самые популярные сайты типа Twitter, через которых мы планируем «представиться» нашему сайту. Провайдер публикует информацию о том, к каким адресам надо обращаться, чтобы успешно пройти аутентификацию.
Консумер (consumer
) — клиент, запрашивающий разрешение на аутентификацию. Это наш сайт. Ключевыми характеристиками консумера являются публичный (key
) и секретный (secret
) ключи. Они выдаются сайту-консумеру при регистрации приложения на сервисе-провайдере.
Токен (token
) — условный объект, содержащий информацию для получения доступа к данным провайдера. Токен может быть промежуточным, то есть доступ еще не получен, а сам токен просто хранит данные для следующих этапов аутентификации.
Этапы аутентификации
Рассматриваемый нами механизм предусматривает три этапа:
- 0 (подготовка). Приложение-консумер должно быть зарегистрировано у провайдера. Получаем ключи
key
иsecret
, указываем информацию о приложении (адрес сайта, название и описание и т.д.). - 1. Консумер отправляет провайдеру запрос на получение токена запроса (
request token
). Чтобы провайдер знал, кто к нему обращается, консумер в запросе отправляет параметрoauth_consumer_key
, т.е. публичный ключ, полученный от провайдера. Чтобы запрос не мог быть подделан кем-то еще, дополнительно передается захэшированная строка (алгоритм определяет провайдер) на базе секретного кода консумера, который по идее известен только консумеру и провайдеру.Если все успешно, то полученный токен запроса будет в дальнейшем использован для идентификации провайдером. Проще говоря, провайдер выдает клиенту временный ключ (случайная строка), по которому он позже поймет, кто (и в рамках какого процесса идентификации) к нему обращается.
- 2. Консумер с провайдером «договорился», теперь надо, чтобы представился собственно пользователь. Для этого мы должны перенаправить его на страницу провайдера, адрес которой мы узнаем при регистрации консумера (или в документации провайдера). Дополнительно передаем в адресе полученный в п1 токен запроса.
На сайте провайдера пользователь вводит свой логин и пароль, если он еще не залогинен, и перенаправляется обратно на сайт консумера. Куда именно редиректить? Это указывает консумер, обычно еще на первом этапе, но иногда достаточно указать нужный адрес при регистрации.
- 3. В адресе для редиректа передается параметр
oauth_verifier
, это код верификации. Последним шагом надо отправить провайдеру запрос на токен доступа (access token
). В запросе передаются все тот же публичный ключ консумера, полученный ранее токен запроса, а также код верификации. Тут запрос также «подписывается», т.е. передается хэш-строка на базе секретного ключа и токена запроса. Это называется обменом токена запроса на токен доступа.В результате провайдер отдает токен доступа, который в дальнейшем можно использовать для работы с аккаунтом (не забываем, что OAuth на самом деле протокол для авторизации, т.е. помимо идентификации пользователя можно получать права на совершение различных действий, например отправить твит в Twitter).
Как все это сложно!
Да, не самая простая схема. Давайте попробуем ее рассмотреть на примере следующей картинки из документации по OAuth от Google (кликабельно).
- 1. Отправляем провайдеру запрос на получение токена запроса.
- 2. Получаем в ответ временный токен запроса (unauthorized — потому что собственно авторизация не произошла еще).
- 3. Генерируем адрес провайдера для редиректа на него пользователя.
- 4, 5. Пользователь на сайте провайдера проходит аутентификацию. При этом провайдер его честно предупреждает о намерениях консумера (например, доступ к адресной книге — настраивается при регистрации приложения, либо передается в качестве дополнительных параметров в п.1).
- 6. Провайдер отправляет пользователя обратно на сайт консумера.
- 7. Отправляем запрос на обмен токена запроса на токен доступа.
- 8. Если все в порядке, то получаем в ответ токен доступа.
- 9, 10. Использование токена доступа для работы с сервисами Google.
А что Kohana?
В Kohana v3 достаточно давно имеется модуль OAuth, созданный Shadowhand‘ом. На данный момент он может работать с Twitter и Google, но написать собственный драйвер для другого провайдера несложно. Вот как может выглядеть абстрактный контроллер для работы с OAuth v1:
abstract class Controller_OAuth extends Controller { /** * @var OAuth_Token токен (access или request) */ protected $_token; /** * @var OAuth_Provider провайдер */ protected $_provider; /** * @var OAuth_Consumer консумер */ protected $_consumer; /** * @var string имя куки для хранения токенов */ protected $_cookie; protected $_config; /** * @var array дополнительные параметры для запроса request token */ protected $_request_params = array(); /** * @var array дополнительные параметры для запроса access token */ protected $_access_params = array(); /** * @var string имя провайдера (надо прописать в дочернем контроллере, например 'twitter') */ public $name; public function before() { parent::before(); // загружаем конфиг OAuth с ключами консумера $this->_config = Kohana::config('oauth.'.$this->name); // создаем консумера $this->_consumer = OAuth_Consumer::factory($this->_config); // определяем имя для куки $this->_cookie = 'oauth_cookie_'.$this->name; // создаем провайдера $this->_provider = OAuth_Provider::factory($this->name, $this->_config); if ($token = Cookie::get($this->_cookie)) { // в куке уже может лежать токен $this->_token = unserialize($token); } } /** * Стартовая точка аутентификации. * * Получаем request token и редиректим пользователя на страницу провайдера */ public function action_login() { // в объекте Consumer сохраняем УРЛ для возврата (он будет передан провайдеру) $this->_consumer->callback($this->request->url(array('action' => 'complete'), 'http')); // пытаемся получить request token $token = $this->_provider->request_token($this->_consumer, $this->_request_params); // сохраняем request token в куке Cookie::set($this->_cookie, serialize($token)); // направляем пользователя на страницу логина провайдера $this->request->redirect($this->_provider->authorize_url($token)); } /** * Заканчиваем аутентификацию. Надо обменять полученный ранее токен запроса на токен доступа */ public function action_complete() { if ($this->_token AND $this->_token->token() !== Arr::get($_GET, 'oauth_token')) { // Полученный от провайдера ключ токена запроса не сходится с имеющимся Cookie::delete($this->_cookie); // Начинаем заново $this->request->redirect($this->request->uri(array('action' => 'login'))); } // Получаем код верификации из GET-строки $verifier = Arr::get($_GET, 'oauth_verifier'); // Сохраняем код в токене (он там еще понадобится) $this->_token->verifier($verifier); // Меняем request token на access token $this->_token = $this->_provider->access_token($this->_consumer, $this->_token, $this->_access_params); // Сохраняем токен в куке - дальше он будет нужен для работы с аккаунтом через OAuth Cookie::set($this->_cookie, serialize($this->_token)); // перенаправляем куда-нибудь - аутентификация закончена, токен получен $this->request->redirect('/'); } } |
Для каждого отдельного провайдера создаем контроллер, например для Google:
class Controller_OAuth_Google extends Controller_OAuth { protected $_request_params = array( // для Google надо обязательно передавать параметр oauth_scope! 'scope' => 'http://www-opensocial.googleusercontent.com/api/people/', ); // не забываем указать, что это провайдер Google public $name = 'google'; } |
Ну и конечно, надо не забыть заполнить конфигурационный файл oauth.php
, в котором вписать данные консумера (публичный и секретные ключи):
// config/oauth.php return array( 'google' => array( 'key' => 'публичный_ключ', 'secret' => 'секретный ключ', ), ) |
Чтобы добавить еще провайдера, надо всего лишь создать два файла + добавить настройки в конфиг. Так, для провайдера Google прописаны следующие классы:
class Kohana_OAuth_Provider_Google extends OAuth_Provider { public $name = 'google'; protected $signature = 'HMAC-SHA1'; public function url_request_token() { return 'https://www.google.com/accounts/OAuthGetRequestToken'; } public function url_authorize() { return 'https://www.google.com/accounts/OAuthAuthorizeToken'; } public function url_access_token() { return 'https://www.google.com/accounts/OAuthGetAccessToken'; } public function request_token(OAuth_Consumer $consumer, array $params = NULL) { if ( ! isset($params['scope'])) { // All request tokens must specify the data scope to access // http://code.google.com/apis/accounts/docs/OAuth.html#prepScope throw new Kohana_OAuth_Exception('Required parameter to not passed: :param', array( ':param' => 'scope', )); } return parent::request_token($consumer, $params); } } // End OAuth_Provider_Google |
В данном классе добавлены специфические свойства провайдера (имя $name
, способ хэширования $signature
), методы для получения URL провайдера (url_request_token()
, url_authorize()
и url_access_token()
). Так как в случае с Google параметр oauth_scope
при получении токена запроса является обязательным, добавлена проверка на его наличие в методе request_token()
.
Второй класс — стандартная пустышка в classes/oauth/provider/google.php
.
Что, все?
Сейчас разрабатывается вторая версия спецификации OAuth, в частности ее реализация есть в Github. Недавно я отправил Shadowhand‘у pull request
на включение в модуль OAuth реализации данной версии. Если она будет принята, скоро появится статья об OAuth v2
Собственно данной темой я заинтересовался в рамках нашей разработки сайта kohana-world.com
, где мы хотим осуществлять аутентификацию полностью средствами сторонних сервисов. В том числе и с написанием собственного модуля Auth с поддержкой OAuth и OpenID. По прошествии некоторого времени я напишу о результатах работы в данном направлении.
Спасибо за статью!
Нужно заметить что Facebook сейчас 2 версию использует, если я не ошибаюсь.
Кстати, нашел небольшой список провайдеров OAuth.
http://www.reijo.org/scribbles/list-of-oauth-service-providers
Интересно а в рунете как с этим?
Сам еще OAuth не использовал, но начало уже не за горами
@Chodex
Из русских вроде бы Яндекс и Майл.ру работают на OAuth v2.
На прошлой неделе вконтакт наконец реализовал OAuth2. Ещё б одноклассники бы сделали, и был бы мир на просторах рунета.
Про openid лучше забыть, и не вспоминать как про дурной сон.
**На прошлой неделе вконтакт наконец реализовал**
Ну еще бы…
Теперь вся инфа в одном месте
—
Веселее пишите свою историю, ребята…
Хотел спросить, почему обычная Аутх на експлорере не работает?
@qwenchi
Единственное, что при работе Auth зависит от браузера — это работа сессии. Проверьте, она вообще работает (сохранили в сессию значение, после редиректа пробуем его считать)? Обычно это связано с неправильной настройкой кук.