Контент


OAuth v1

Похоже, что проходят времена, когда каждый сайт требовал от пользователя зарегистрироваться. Теперь в моде аутентификация/авторизация через популярные ресурсы типа 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. По прошествии некоторого времени я напишу о результатах работы в данном направлении.

Google Bookmarks Digg Reddit del.icio.us Ma.gnolia Technorati Slashdot Yahoo My Web News2.ru БобрДобр.ru RUmarkz Ваау! Memori.ru rucity.com МоёМесто.ru Mister Wong

Опубликовано в Kohana3.

Теги: , , .


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

Будьте в курсе обсуждения, подпишитесь на RSS ленту комментариев к этой записи.

  1. Chodex пишет:

    Спасибо за статью!
    Нужно заметить что Facebook сейчас 2 версию использует, если я не ошибаюсь.
    Кстати, нашел небольшой список провайдеров OAuth.
    http://www.reijo.org/scribbles/list-of-oauth-service-providers

    Интересно а в рунете как с этим?

    Сам еще OAuth не использовал, но начало уже не за горами :)

  2. biakaveron пишет:

    @Chodex

    Из русских вроде бы Яндекс и Майл.ру работают на OAuth v2.

  3. alkhankhel пишет:

    На прошлой неделе вконтакт наконец реализовал OAuth2. Ещё б одноклассники бы сделали, и был бы мир на просторах рунета.

    Про openid лучше забыть, и не вспоминать как про дурной сон.

  4. Zares пишет:

    **На прошлой неделе вконтакт наконец реализовал**
    Ну еще бы…
    Теперь вся инфа в одном месте :)

    Веселее пишите свою историю, ребята…

  5. qwenchi пишет:

    Хотел спросить, почему обычная Аутх на експлорере не работает?

  6. biakaveron пишет:

    @qwenchi
    Единственное, что при работе Auth зависит от браузера — это работа сессии. Проверьте, она вообще работает (сохранили в сессию значение, после редиректа пробуем его считать)? Обычно это связано с неправильной настройкой кук.

Продолжение обсуждения

  1. OAuth v2 | Изучаем Web ссылается на эту запись on 24 мая 2011

    [...] так давно я написал статью “OAuth v1“, в которой рассказыл об основах работы с OAuth с [...]



Можно включить подсветку кода: <code><pre lang="">...</pre></code>
Разрешены некоторые HTML теги

или используйте trackback.