Oauth

Содержание

OAuth 2.0 простым и понятным языком


На хабре уже писали про OAuth 1.0, но понятного объяснения того, что такое OAuth 2.0 не было. Ниже я расскажу, в чем отличия и преимущества OAuth 2.0 и, как его лучше использовать на сайтах, в мобильных и desktop-приложениях.

Что такое OAuth 2.0

OAuth 2.0 — протокол авторизации, позволяющий выдать одному сервису (приложению) права на доступ к ресурсам пользователя на другом сервисе. Протокол избавляет от необходимости доверять приложению логин и пароль, а также позволяет выдавать ограниченный набор прав, а не все сразу.

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

Не смотря на то, что объяснений на эту тему уже было много, она по-прежнему вызывает некоторое непонимание.
OpenID предназначен для аутентификации — то есть для того, чтобы понять, что этот конкретный пользователь является тем, кем представляется. Например, с помощью OpenID некий сервис Ололо может понять, что зашедший туда пользователь, это именно Рома Новиков с Mail.Ru. При следующей аутентификации Ололо сможет его опять узнать и понять, что, это тот же Рома, что и в прошлый раз.
OAuth же является протоколом авторизации, то есть позволяет выдать права на действия, которые сам Ололо сможет производить в Mail.Ru от лица Ромы. При этом Рома после авторизации может вообще не участвовать в процессе выполнения действий, например, Ололо сможет самостоятельно заливать фотографии на Ромин аккаунт.

Как работает OAuth 2.0

Как и первая версия, OAuth 2.0 основан на использовании базовых веб-технологий: HTTP-запросах, редиректах и т. п. Поэтому использование OAuth возможно на любой платформе с доступом к интернету и браузеру: на сайтах, в мобильных и desktop-приложениях, плагинах для браузеров…
Ключевое отличие от OAuth 1.0 — простота. В новой версии нет громоздких схем подписи, сокращено количество запросов, необходимых для авторизации.
Общая схема работы приложения, использующего OAuth, такова:

  1. получение авторизации
  2. обращение к защищенным ресурсам

Результатом авторизации является access token — некий ключ (обычно просто набор символов), предъявление которого является пропуском к защищенным ресурсам. Обращение к ним в самом простом случае происходит по HTTPS с указанием в заголовках или в качестве одного из параметров полученного access token’а.
В протоколе описано несколько вариантов авторизации, подходящих для различных ситуаций:

  • авторизация для приложений, имеющих серверную часть (чаще всего, это сайты и веб-приложения)
  • авторизация для полностью клиентских приложений (мобильные и desktop-приложения)
  • авторизация по логину и паролю
  • восстановление предыдущей авторизации

Авторизация для приложений, имеющих серверную часть

  1. Редирект на страницу авторизации
  2. На странице авторизации у пользователя запрашивается подтверждение выдачи прав
  3. В случае согласия пользователя, браузер редиректится на URL, указанный при открытии страницы авторизации, с добавлением в GET-параметры специального ключа — authorization code
  4. Сервер приложения выполняет POST-запрос с полученным authorization code в качестве параметра. В результате этого запроса возвращается access token

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

Пример

Здесь и далее примеры приводятся для API Mail.Ru, но логика одинаковая для всех сервисов, меняются только адреса страниц авторизации. Обратите внимание, что запросы надо делать по HTTPS.
Редиректим браузер пользователя на страницу авторизации:
> GET /oauth/authorize?response_type=code&client_id=464119& redirect_uri=http%3A%2F%2Fexample.com%2Fcb%2F123 HTTP/1.1 > Host: connect.mail.ru
Здесь и далее, client_id и client_secret — значения, полученные при регистрации приложения на платформе.
После того, как пользователь выдаст права, происходит редирект на указанный redirect_uri:
< HTTP/1.1 302 Found < Location: http://example.com/cb/123?code=DoRieb0y
Обратите внимание, если вы реализуете логин на сайте с помощью OAuth, то рекомендуется в redirect_uri добавлять уникальный для каждого пользователя идентификатор для предотвращения CSRF-атак (в примере это 123). При получении кода надо проверить, что этот идентификатор не изменился и соответствует текущему пользователю.
Используем полученный code для получения access_token, выполняя запрос с сервера:
> POST /oauth/token HTTP/1.1 > Host: connect.mail.ru > Content-Type: application/x-www-form-urlencoded > > grant_type=authorization_code&client_id=464119&client_secret=deadbeef&code=DoRieb0y& redirect_uri=http%3A%2F%2Fexample.com%2Fcb%2F123 < HTTP/1.1 200 OK < Content-Type: application/json < < { < «access_token»:»SlAV32hkKG», < «token_type»:»bearer», < «expires_in»:86400, < «refresh_token»:»8xLOxBtZp8″, < }
Обратите внимание, что в последнем запросе используется client_secret, который в данном случае хранится на сервере приложения, и подтверждает, что запрос не подделан.
В результате последнего запроса получаем сам ключ доступа (access_token), время его «протухания&raquo (expires_in), тип ключа, определяющий как его надо использовать, (token_type) и refresh_token о котором будет подробнее сказано ниже. Дальше, полученные данные можно использовать для доступа к защищенным ресурсам, например, API Mail.Ru:
> GET /platform/api?oauth_token=SlAV32hkKG&client_id=464119&format=json&method=users.getInfo& sig=… HTTP/1.1 > Host: appsmail.ru
Описание в спецификации

Авторизация полностью клиентских приложений

  1. Открытие встроенного браузера со страницей авторизации
  2. У пользователя запрашивается подтверждение выдачи прав
  3. В случае согласия пользователя, браузер редиректится на страницу-заглушку во фрагменте (после #) URL которой добавляется access token
  4. Приложение перехватывает редирект и получает access token из адреса страницы

Этот вариант требует поднятия в приложении окна браузера, но не требует серверной части и дополнительного вызова сервер-сервер для обмена authorization code на access token.

Открываем браузер со страницей авторизации:
> GET /oauth/authorize?response_type=token&client_id=464119 HTTP/1.1 > Host: connect.mail.ru
После того, как пользователь выдаст права, происходит редирект на стандартную страницу-заглушку, для Mail.Ru это connect.mail.ru/oauth/success.html:
< HTTP/1.1 302 Found < Location: http://connect.mail.ru/oauth/success.html#access_token=FJQbwq9&token_type=bearer& expires_in=86400&refresh_token=yaeFa0gu
Приложение должно перехватить последний редирект, получить из адреса acess_token и использовать его для обращения к защищенным ресурсам.
Описание в спецификации

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

Авторизация по логину и паролю представляет простой POST-запрос, в результате которого возвращается access token. Такая схема не представляет из себя ничего нового, но вставлена в стандарт для общности и рекомендуется к применению только, когда другие варианты авторизации не доступны.

> POST /oauth/token HTTP/1.1 > Host: connect.mail.ru > Content-Type: application/x-www-form-urlencoded > > grant_type=password&client_id=31337&client_secret=deadbeef&username=api@corp.mail.ru& password=qwerty < HTTP/1.1 200 OK < Content-Type: application/json < < { < «access_token»:»SlAV32hkKG», < «token_type»:»bearer», < «expires_in»:86400, < «refresh_token»:»8xLOxBtZp8″, < }
Описание в спецификации

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

Обычно, access token имеет ограниченный срок годности. Это может быть полезно, например, если он передается по открытым каналам. Чтобы не заставлять пользователя проходить авторизацию после истечения срока действия access token’а, во всех перечисленных выше вариантах, в дополнение к access token’у может возвращаться еще refresh token. По нему можно получить access token с помощью HTTP-запроса, аналогично авторизации по логину и паролю.

> POST /oauth/token HTTP/1.1 > Host: connect.mail.ru > Content-Type: application/x-www-form-urlencoded > > grant_type=refresh_token&client_id=31337&client_secret=deadbeef&refresh_token=8xLOxBtZp8 < HTTP/1.1 200 OK < Content-Type: application/json < < { < «access_token»:»Uu8oor1i», < «token_type»:»bearer», < «expires_in»:86400, < «refresh_token»:»ohWo1ohr», < }
Описание в спецификации

Минусы OAuth 2.0

Во всей этой красоте есть и ложка дегтя, куда без нее?
OAuth 2.0 — развивающийся стандарт. Это значит, что спецификация еще не устоялась и постоянно меняется, иногда довольно заметно. Так, что если вы решили поддержать стандарт прямо сейчас, приготовьтесь к тому, что его поддержку придется подпиливать по мере изменения спецификации. С другой стороны, это также значит, что вы можете поучаствовать в процессе написания стандарта и внести в него свои идеи.
Безопасность OAuth 2.0 во многом основана на SSL. Это сильно упрощает жизнь разработчикам, но требует дополнительных вычислительных ресурсов и администрирования. Это может быть существенным вопросом в высоко нагруженных проектах.

Заключение

OAuth — простой стандарт авторизации, основанный на базовых принципах интернета, что делает возможным применение авторизации практически на любой платформе. Стандарт имеет поддержку крупнейших площадок и очевидно, что его популярность будет только расти. Если вы задумались об API для вашего сервиса, то авторизация с использованием OAuth 2.0 — хороший выбор.
Со своей стороны, мы внедрили OAuth 2.0 в API Mail.Ru и, теперь, вы можете использовать возможности протокола для реализации любых клиентов и сервисов, интегрированных с Mail.Ru.

Ссылки

  • Текущая версия драфта стандарта OAuth 2.0
  • Официальный сайт OAuth
  • Рабочая группа по выработке стандарта (архивы)
  • Документация по реализации OAuth 2.0 в Mail.Ru

Дмитрий Битман — менеджер Платформы@Mail.Ru

Применение

Одной из проблем аутентификации и информационной безопасности является тот факт, что у одного пользователя, как правило, имеется несколько учётных записей на различных сервисах (например, на Google, Twitter, Apple и др.). При этом человеку приходится для каждого из сервисов иметь отдельные логин и пароль. В то же время, каждый из сервисов имеет свою систему безопасности, каждая из которых имеет свои уязвимости и недостатки. Как следствие, пользователям нужно хранить и защищать множество логинов-паролей, что вредит как удобству, так и безопасности; когда, например, пользователи для разных сервисов начинают использовать одни и те же пароли. В то же время, в данных обстоятельствах злоумышленникам становится проще осуществлять взлом (например, после получения доступа к одной из учётных записей взлом других может быть облегчён).

Для улучшения защиты может быть использована аутентификация в два шага (например Google Authenticator), когда для подтверждения задействуется другой сервис пользователя (например, когда для аутентификации на веб-сайте требуется ввести кодовое слово, отправляемое на мобильный телефон). В этом случае вероятность взлома значительно уменьшается. Ключевая особенность применения OAuth заключается в том, что, если пользователь имеет подобный хорошо защищённый аккаунт, то с его помощью и технологии OAuth он может пройти аутентификацию на других сервисах, и при этом ему не требуется раскрывать свой основной пароль. Например, пользователь, который хочет предоставить онлайн-сервису печати фотографий доступ к фотографиям своего Facebook-аккаунта, не должен сообщать сервису пароль от этого аккаунта. Вместо этого он проходит авторизацию непосредственно в Facebook’е, который (с разрешения пользователя или администратора сервиса) предоставляет сервису онлайн-печати полномочия доступа к фотографиям.

История

OAuth 1.0

OAuth появился в ноябре 2006 года, во время разработки Блейном Куком (англ. Blaine Cook) протокола OpenID для сервиса микроблогов Twitter. Совместно с Крисом Мессиной (англ. Chris Messina) он искал способ использования OpenID для доступа к Twitter API без предоставления сервису пароля. В сотрудничестве с одним из создателей OpenID Девидом Рекордоном (англ. David Recordon) они провели анализ функциональности OpenID, а также проприетарных протоколов авторизации, таких как Flickr Auth, Google AuthSub и Yahoo! BBAuth, после чего пришли к заключению, что существует необходимость в новом открытом протоколе.

В апреле 2007 года образовалась группа инженеров, работавших над его созданием. В её работе приняли участие сотрудники компаний Google и AOL (которая в это же время представила свой собственный протокол OpenAuth). Финальная версия ядра протокола OAuth 1.0 была представлена 4 декабря 2007 года. В 2008 году проводилась работа по стандартизации протокола в Инженерном совете Интернета.

15 апреля 2009 года Twitter предложил своим пользователям решение, позволяющее делегировать третьесторонним сайтам и сервисам доступ к своим аккаунтам. Оно было названо «Войти через Твиттер» и было основано на OAuth. Это событие стало поводом для первого широкого исследования протокола на уязвимости, и через несколько дней была обнаружена потенциальная уязвимость, затрагивающая все существующие реализации OAuth. После этого, 23 апреля сообществом разработчиков было выпущено первое дополнение безопасности к протоколу, которое вошло в обновлённую спецификацию OAuth Core 1.0 Revision A, опубликованную 24 июня. В апреле 2010 года был выпущен информационный документ RFC 5849, посвящённый стандарту OAuth.

OAuth 2.0

В 2010 году началась работа над новой версией протокола OAuth 2.0, для которой не предусматривалась обратная совместимость с OAuth 1.0. В октябре 2012 года структура OAuth 2.0 была опубликована в RFC 6749, и использование носителя токена в RFC 6750.

Предпосылок для создания OAuth 2.0 было несколько. В первую очередь, OAuth достаточно нетривиально использовать на клиентской стороне. Одна из поставленных целей при разработке нового OAuth — упростить разработку клиентских приложений. Во-вторых, несмотря на заявленную в стандарте реализацию трёх методов (называемых потоками (англ. flows)) получения токена (уникального идентификатора) для авторизации: для веб-приложений, настольных клиентов и мобильных клиентов, фактически все три способа слиты в один. И, в-третьих, протокол оказался плохо масштабируемым. В него, помимо новых потоков, были добавлены:

  • Токен на предъявителя. Метод авторизации аналогичен существующему способу авторизации с помощью cookie. В этом случае токен непосредственно используется как секрет (сам факт наличия токена авторизует клиента) и передаётся через HTTPS. Это позволяет получать доступ к API посредством простых скриптов (например, с использованием cURL).
  • Упрощенная подпись. Подпись была значительно упрощена, чтобы устранить необходимость в специальном анализе, кодированиях и сортировках параметров.
  • Короткоживущие токены с долговременной авторизацией. Вместо выдачи долгоживущего токена (который за длительное время может быть скомпрометирован), сервер предоставляет кратковременный доступ и долговременную возможность обновлять токен без участия пользователя.
  • Разделение ролей. За авторизацию и за предоставление доступа к API могут отвечать разные сервера.

Несмотря на то, что стандарт OAuth 2.0 не утверждён, он используется некоторыми сервисами. Так, авторизация в Mail.Ru API переведена на стандарт OAuth 2.0, Facebook Graph API (англ.)русск. поддерживает только OAuth 2.0., а Google рассматривает OAuth 2.0 как рекомендательный механизм аутентификации для всех своих API.

Последующее развитие

В июле 2012 года Эран Хаммер (Eran Hammer), действующий редактор стандарта OAuth 2.0, объявил об уходе с поста после трёх лет работы над новым стандартом и попросил вычеркнуть своё имя из спецификаций. Он говорил о своих взглядах на своём сайте и позже выступил с докладом. Девид Рекордон (англ. David Recordon) позже также вычеркнул своё имя из спецификаций без указания причин. Дик Хардт (Dick Hardt) стал редактором стандарта OAuth2.0, и, несмотря на взгляды Эрана Хаммера, структура OAuth 2.0 была опубликована в RFC 6749 в октябре 2012 года.

Сравнение с другими протоколами

Преимущества

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

  • У пользователя больше оснований доверять приложению, поскольку пользователь может быть уверен, что несанкционированный доступ к его личным данным невозможен. Не владея логином и паролем пользователя, приложение сможет выполнять только те действия с данными, которые разрешил пользователь, и никакие другие.
  • При разработке приложения не нужно заботиться об обеспечении конфиденциальности логина и пароля пользователя. Логин и пароль не передаются приложению, а следовательно, они не могут попасть в руки злоумышленников.

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

  • Если пользователь изменяет пароль, то приложение больше не может получить доступ к защищённым ресурсам.
  • Единственный способ запретить приложению доступ к защищённым ресурсам — изменить пароль. Это одновременно запретит доступ к ресурсам и другим приложениям, которые ранее его имели.
  • Сервисы, хранящие защищённые ресурсы и предоставляющие API для доступа к ним, могут использовать федеративные механизмы аутентификации (англ. Federated Authentication), такие как OpenID или SAML, что позволяет пользователям не иметь пароля к их аккаунтам. Это делает невозможным для этих пользователей использование приложений, запрашивающих доступ к защищённым ресурсам через этот API.

Отличие от OpenID

Хотя OAuth и OpenID имеют много общего, OAuth является самостоятельным протоколом, никак не связанным с OpenID:

  • OAuth является протоколом авторизации, который позволяет предоставить права на использование какого-то ресурса (например, API какого-либо сервиса). Наличие прав определяется токеном (уникальным идентификатором), который может быть одним и тем же для разных пользователей, или же у одного пользователя в разное время могут быть разные токены. Предоставление прав происходит в обмен на предоставление токена. В общем случае нельзя определить, кому принадлежит токен и кто в настоящий момент пользуется правами.
  • OpenID является средством аутентификации: с помощью этой системы можно удостовериться, что пользователь — именно тот, за кого себя выдаёт. Какие действия сможет совершать пользователь, прошедший аутентификацию посредством OpenID, определяется стороной, проводящей аутентификацию.

Описание протоколов OAuth

Рассмотрим основные понятия протоколов и примеры их использования.

Клиент, сервер и владелец ресурса

OAuth 1.0 определяет три роли: клиент, сервер и владелец ресурса. Эти три роли присутствуют в любой операции OAuth, в некоторых случаях клиент также является владельцем ресурса. Оригинальная версия спецификации использует различный набор терминов для этих ролей: потребитель (клиент), услуга (сервер) и пользователь (владелец ресурса). В протоколе OAuth 2.0 произошло разделение ролей сервера: отдельно выделяется сервер авторизации и сервер, хранящий защищённые ресурсы.

Методы создания подписей

OAuth поддерживает 3 метода создания подписей, используемых для подписывания и проверки запросов: PLAINTEXT, HMAC-SHA1 и RSA-SHA1. PLAINTEXT тривиален в использовании и занимает значительно меньше времени для вычисления, но он может быть безопасен только над HTTPS или аналогичными защищёнными каналами. HMAC-SHA1 предлагает простой и общий алгоритм, который доступен на большинстве платформ, но не на всех устаревших устройствах, и использует симметричный общий ключ. RSA-SHA1 обеспечивает повышенную безопасность с помощью пары ключей, но является более сложным и требует генерации ключей.

Метка времени и Nonce

Чтобы предотвратить угрозу повторного использования запросов, OAuth использует nonce и метку времени. Термин «nonce» означает одноразовый код и является случайным набором букв и цифр, который предназначен для уникальной идентификации каждого подписанного запроса. Имея уникальный идентификатор для каждого запроса, поставщик услуг сможет предотвратить повторноe использование запросов. Это реализуется в виде генерируемой клиентом уникальной строки для каждого отправляемого на сервер запроса. А сервер отслеживает все использованные nonce для предотвращения их использования во второй раз.

Использование nonce может быть очень дорогостоящим для сервера, так как они требуют постоянного хранения всех полученных nonce. Для того, чтобы реализация была проще, OAuth добавляет метку времени для каждого запроса, которая позволяет серверу сохранить nonce в течение ограниченного времени. Если сервер получает запрос с меткой времени более ранней, чем сохранённое время, то он отвергает его как если бы не имел nonce с такого времени.

Полномочия и токены

В OAuth 1.0 и OAuth 2.0 используются три вида полномочий: учётные данные клиента (англ. consumer key and secret или англ. client credentials), временные учётные данные (англ. request token and secret или англ. temporary credentials) и токены (англ. access token and secret или англ. token credentials).

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

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

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

Схема работы протокола OAuth

Поясним работу протокола OAuth 1.0 на примере. Допустим, что пользователь (владелец ресурсов) хочет распечатать свои фотографии (ресурсы), загруженные на сайт «photos.example.net» (сервер), при помощи сервиса печати «printer.example.net» (клиент).

  1. Клиент посредством протокола HTTPS отправляет серверу запрос, который содержит идентификатор клиента, метку времени, адрес обратного вызова (по нему нужно вернуть токен), используемый тип цифровой подписи и саму подпись.
  2. Сервер подтверждает запрос и отвечает клиенту токеном запроса (англ. Request Token) и частью разделённого секрета.
  3. Клиент передаёт токен владельцу ресурсов (пользователю) и перенаправляет его на сервер для прохождения аутентификации.
  4. Сервер, получив от пользователя токен, запрашивает у него его логин и пароль, и в случае успешной аутентификации просит пользователя подтвердить доступ клиента к ресурсам (происходит авторизация), после чего пользователь перенаправляется сервером к клиенту.
  5. Клиент передаёт серверу токен запроса посредством протокола TLS и запрашивает доступ к ресурсам.
  6. Сервер подтверждает запрос и отвечает клиенту новым токеном доступа (англ. Access Token).
  7. Используя токен доступа, клиент обращается к серверу за ресурсами.
  8. Сервер подтверждает запрос и предоставляет ресурсы.

Протокол OAuth 2.0 обратно не совместим с протоколом OAuth 1.0. Вместо того, чтобы дополнить OAuth 1.0, было принято решение разработать другой протокол. Поэтому принцип работы OAuth 1.0 и OAuth 2.0 отличается. Так в стандарте OAuth 2.0 описаны следующие потоки (сценарии взаимодействия сторон):

  • Поток неявного доступа (англ. Implicit Grant Flow)

Является самым коротким потоком протокола: пользователь сначала перенаправляется клиентом на сервер, чтобы разрешить доступ к ресурсам, а после того, как доступ будет получен, перенаправляется сервером обратно к клиенту.

  • Поток с кодом подтверждения (англ. Authorization Code Flow)

Отличие данного потока от потока неявного доступа заключается в дополнительном шаге аутентификации клиента.

  • Поток с обновляемым токеном (англ. Refreshing an Expired Access Token Flow)

Отличия этого потока от приведённого примера в следующем: на шаге 2 сервер помимо токена доступа, который имеет ограниченное время жизни, выдаёт токен обновления; на шаге 8 сервер проверяет, является ли токен доступа валидным (в смысле истечения времени жизни), и в зависимости от этого либо предоставляет доступ к ресурсам, либо требует обновления токена доступа (который предоставляется при предъявлении токена обновления).

  • Поток с предоставлением клиенту пароля (англ. Resource Owner Password Credentials Flow)

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

  • Поток клиентских полномочий (англ. Client Credentials Flow)

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

OAuth поддерживает два метода аутентификации сообщений от клиента: HMAC-SHA1 и RSA-SHA1. Есть возможность передавать сообщения без подписи, тогда в поле типа подписи указывается «plain text». Но в этом случае, согласно спецификации, соединение между клиентом и сервером должно устанавливаться через протокол SSL или TLS.

> Примечания

oauth-2.0 : Пример клиента и сервера PHP OAuth 2.0

Настроить поставщика OAuth2 довольно просто, если вы знаете, как работает протокол. Это 2-х или 3-х шаговый процесс (в зависимости от вашей настройки и того, получаете ли вы токены от имени пользователя или просто с сервера).

Что вам нужно:

  • Рабочий код для провайдера OAuth2
  • Терпение

Что вам нужно выяснить, как сделать с вашим кодом:

  • Создать клиента (токены общего и частного доступа)
  • Выясните, как называются конечные точки авторизации и токена (обычно /authorize и /token )
  • Выясните, как обрабатываются области

Первым шагом для получения токена является вызов /authorize?response_type=code&client_id=&redirect_uri=&scope= , где:

  • clientid () — ваш токен общего доступа
  • redirect_uri () — это ваш URI перенаправления. Вы будете перенаправлены на это после завершения шага авторизации
  • область действия — это область вашего будущего токена

По завершении (обычно это кнопка отправки), ваш браузер будет перенаправлен на URI, указанный с помощью кода в URL (code = blah). Сохраните это значение.

Получив этот код, вызовите другую конечную точку: /token?client_id=&client_secret=&grant_type=authorization_code&scope=&code=&redirect_uri=

Параметры: — client_id — опять же, ваш открытый ключ клиента — client_secret — ваш закрытый ключ ( это должен быть вызов на стороне сервера ) — область действия — область действия токена — ДОЛЖЕН СООТВЕТСТВОВАТЬ ПЕРВОМУ ВЫЗОВУ — redirect_uri — URI перенаправления — ДОЛЖЕН СООТВЕТСТВОВАТЬ ПЕРВОМУ ЗВОНКУ — код — код, который вы получили

Если все прошло хорошо, на экране вы увидите объект JSON, содержащий информацию о токене.

Пример клиента и сервера OAuth 2.0 PHP

Настройка поставщика OAuth2 довольно проста, как только вы знаете, как работает протокол. Это 2-х или 3-х шаговый процесс (в зависимости от вашей настройки и от того, получаете ли вы токены от имени пользователя или только от сервера).

Что вам понадобится:

  • Рабочий код для поставщика OAuth2.
  • Patience

Что вам нужно, чтобы выяснить, как сделать код:

  • Создать клиентский токен (общедоступный и закрытый токены)
  • Выясните, как названы конечные точки авторизации и токена (обычно /authorize и /token)
  • Выясните, как обрабатываются области действия.

Первым шагом для получения токена является вызов /authorize?response_type=code&client_id=&redirect_uri=&scope=, где:

  • clientid () — ваш токен открытого доступа.
  • redirect_uri () — ваш URI перенаправления. Вы будете перенаправлены на это после завершения этапа авторизации. Область
  • — это область вашего будущего токена.

По завершении (обычно кнопка отправки) ваш браузер будет перенаправлен на URI, указанный с кодом в URL (code = blah). Сохраните это значение.

Когда у вас есть этот код, вызовите другую конечную точку: /token?client_id=&client_secret=&grant_type=authorization_code&scope=&code=&redirect_uri=

Параметры: — client_id — снова, ваш открытый ключ клиента — client_secret — ваш закрытый ключ (предполагается, что это вызов на стороне сервера) — scope — область для токена — ДОЛЖНА ОБРАТИТЬ ПЕРВЫЙ ВЫЗОВ — redirect_uri — редирект URI — ДОЛЖЕН СМАТЬ ПЕРВЫЙ ВЫЗОВ — код — полученный код

Если все будет в порядке, на экране появится объект JSON, содержащий информацию о токенах.

Что такое OAuth? Определение термина OAuth

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

Немного истории

OAuth 1.0
OAuth возник еще в ноябре 2006, когда Блейн Кук занимался разработкой реализации OpenID для Твиттера. Вместе с Крисом Мессиной, Блейн искал путь к использованию OpenID для доступа к Twitter API без предоставления сервису пароля. Сотрудничая с одним из разработчиков OpenID Девидом Рекордоном, Кук провел анализ функциональности OpenID и протоколов авторизации, таких как Yahoo! BBAuth, Google AuthSub, Flickr Auth. Был сделан вывод, что необходим новый открытый протокол. Так, в апреле 2007 года образовалась группа разработчиков, которые занялись его созданием. В группе приняли участие сотрудники компаний AOL и Google. Финальную версию протокола OAuth 1.0 была представлена 4 декабря 2007 года, а в 2008 году начались работы по стандартизации протокола.

OAuth 2.0
В 2010 году началась работа над новой версией протокола OAuth 2.0. Главной целью новой версии – упрощение разработки клиентский приложений.

Отличие OAuth от OpenID

Мнение, что OAuth – это расширение протокола OpenID, ошибочно. Хотя OpenID и OAuth имеют много общего, OAuth является протоколом самостоятельным и никак не связанным с OpenID.

OAuth – это протокол авторизации, позволяющий предоставлять права на использование какого-либо ресурса. Наличие прав определяется токеном, который может быть один и тот же для разных юзеров, либо у одного пользователя могут быть разные токены в разное время. Права предоставляются в обмен на предоставление токена.

OpenID – это средство аутентификации. С его помощью можно удостоверится, что пользователь именно тот, за кого он себя выдает.

Схема работы OAuth

Например, пользователь хочет распечатать свои фото, которые загружены на сайт foto.primer.ru с помощью сервиса печати print.primer.ru

  • Клиент с помощью HTTPS протокола отправляет на сервис запрос с содержанием идентификатора клиента, метку времени, адрес обратного вызова, по которому нужно будет вернуть токен, тип цифровой подписи и, непосредственно, саму цифровую подпись
  • Сервер подтверждает запрос и отвечает клиенту токеном доступа и частью разделенного секрета.
  • Клиент осуществляет передачу токена владельцу ресурсов и для прохождения авторизации перенаправляет токен на сервер.
  • Сервер получает токен и запрашивает логин и пароль. Если аутентификация прошла успешна, то просит подтверждения доступа к ресурсам, после чего пользователь перенаправляется сервером к клиенту
  • Клиент передает серверу токен при помощи TLS протокола и запрашивает доступ к ресурсам
  • Сервер подтверждает запрос и отвечает клиенту новым токеном доступа.
  • Клиент использует новый токен для обращения к серверу за ресурсами
  • Сервер подтверждает и предоставляет ресурсы.

Весь список терминов →

Введение в OAuth 2.0

На сегодняшний день это действительно круто иметь возможность поделиться своими записями из Facebook или Google+ с любым сторонним клиентским приложением. Причем сделать так, чтобы ваши записи были доступны только вашим друзьям, так как сервис не только ограничивает нежелательный доступ, но и отслеживает пользователей, основываясь на логинах и паролях.

Здесь в игру и вступает OAuth, который является фреймворком для удаленного делегирования доступов / авторизации, который можно использовать без необходимости дополнительного обмена паролями. По этой причине OAuth часто называют камердинерской связкой ключей от Интернета.

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

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

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

Также стоит отметить, что OAuth не ограничивается только социальными медиа — он обеспечивает формализованный надежный способ обмена информацией между различными видами приложений, которые предоставляют ограниченный доступ к своим функциям.

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

  • Ресурс владельца: приложение, которое способно предоставлять доступ к защищенному ресурсу. Как правило, конечным пользователям;
  • Клиент: приложение, которое отправляет запросы к защищенному ресурсу от имени его владельца и с его разрешения. Это могут быть серверные, мобильные или настольные приложения;
  • Сервер ресурса: сервер защищенного ресурса, способный принимать и отвечать на запросы к ресурсу;
  • Авторизация на сервере: выдача сервером клиенту грантов / маркеров доступа после успешной аутентификации у ресурса владельца и получения от него разрешения;
  • Маркер доступа: маркеры доступа к учетным данным, которые клиент предоставляет серверу ресурса для получения возможности использовать защищенные ресурсы. Обычно это строка параметров, определяющих границы доступа, продолжительности сессии и другие атрибуты. Она также может содержать данные для прохождения авторизации в той или иной форме;
  • Обновление маркера: Хотя это и не предусмотрено по умолчанию, маркеры доступа в идеале должны иметь срок действия (сессии), который может составлять от нескольких минут до нескольких часов. Как только время действия маркера доступа истекло, клиент может запросить сервер об авторизации и выдаче нового маркера доступа с помощью обновления маркера.

В чем была проблема OAuth 1.0?

Главным недостатком OAuth 1.0 была слишком большая сложность данной версии.

На самом деле никаких особых проблем не было! Twitter по-прежнему отлично работает с OAuth 1.0 и просто добавил еще и поддержку версии 2.0. OAuth 1.0 был хорошо продуманной версией фреймворка, которая обеспечивала надежный обмен закрытой информацией, и это делало возможным обмен секретной информацией без подключения SSL.

Причина, почему потребовалась разработка обновления заключается в основном в сложности, с которой была сопряжена работа версии. Ниже приведены некоторые сферы, где OAuth 1.0 не отвечал всем современным требованиям:

  • Подпись каждого запроса: Наличие клиентов, которые генерировали подписи каждого запроса приложений и проверяли на сервере каждый запрос. Это стало серьезным ударом для разработчиков, которые должны были разбирать, кодировать и сортировать параметры, прежде чем отправить запрос.

    В OAuth 2.0 удалось эту сложность обойти. Просто отсылая маркеры по SSL и решая эту задачу на сетевом уровне. OAuth 2.0 не требуется никаких сигнатур;

  • Переадресация встроенных приложений: С развитием встроенных приложений браузеров для мобильных устройств веб-потоки OAuth 1.0 оказались просто неэффективны.

    Так, для них обязательно использование таких агентов как сам интернет-браузер. В OAuth 2.0 потоки были переориентированы именно на работу с встроенными приложениями;

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

OAuth 2.0 в деталях

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

Взамен он получает идентификатор клиента (client_id) и секретный код клиента (client_secret). Этот процесс называется регистрацией клиента. После регистрации клиент может взаимодействовать с сервером по одному из следующих потоков.

Возможные потоки OAuth

OAuth 2.0 добавляет в таблицу порядка пяти новых потоков, что обеспечивает разработчикам гибкость в реализации решений. Они могут использовать любой из них, в зависимости от типа клиента, задействованного в процессе обмена данными:

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

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

  • Поток веб-сервера: Обеспечивает использование гранта кода авторизации. Он представляет собой поток, основанный на принципе перенаправлений, для которого необходимо взаимодействие с агентом конечного пользователя.

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

  • Поток имен пользователей и паролей: Используется только в случае, когда наличествует высокая степень доверия между клиентом и ресурсом владельца, в то время как прочие потоки не в состоянии решить поставленную задачу. Он предполагает передачу клиенту полномочий владельца ресурса.

    В качестве примера клиента для такого рода взаимодействий можно привести операционную систему устройства или приложение с обширными правами доступа. Он также может использоваться для переноса существующих клиентов через протокол HTTP или схемы Digest Authentication в OAuth путем преобразования записанных учетных данных в маркер доступа;

  • Поток утверждения: Ваш клиент может выдавать утверждение, например SAML утверждения, взамен на предоставленный маркер доступа;
  • Поток идентификационной информации клиента: OAuth используется в основном для делегированного доступа, но бывают случаи, когда клиент является одновременно и владельцем ресурса или уже ему были предоставлены права доступа сверх обычных потоков OAuth . Здесь просто происходит обмен предоставленных идентификационных данных клиента на маркер доступа.

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

Но чтобы дать вам больше базовых знаний для их изучения, давайте рассмотрим более глубоко один из наиболее часто используемых потоков: поток веб-сервера.

Поток веб-сервера

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

На диаграмме ниже, в общем, показано, как конечный пользователь (или владелец ресурса) использует клиентское приложение (в данном случае на основе приложения сервера) для аутентификации и авторизации на сервере, для того, чтобы получить доступ к защищенным ресурсам.

Аутентификация и авторизация клиента

Клиент от имени владельца ресурса инициирует поток путем перенаправления к конечной точке авторизации, используя при этом: параметр response_type в качестве кода; идентификатор клиента, который был получен в процессе регистрации клиента; URL-адрес перенаправления; запрошенный объем полномочий (опционально) и текущее состояние (если требуется).

Чтобы получить более наглядное представление о том, как осуществляется весь процесс, на скриншоте ниже графически представлено, как будет выглядеть обработка стандартного запроса / ответа:


Через веб-интерфейс владельцу ресурса обычно представляется следующая панель, в которой он может удостовериться в том, что все разрешения клиентским приложением получены, и оно может использовать закрытые ресурсы от имени их владельца.
Если, клиентом получены гранты владельца ресурса на доступ, сервер авторизации перенаправляет агент пользователя обратно клиенту с помощью параметров перенаправления, которые были предоставлены ранее, вместе с кодом авторизации.

Как это показано на рисунке ниже:

Обмен кода авторизации на маркер

После этого клиент переходит на следующий этап и отправляет код авторизации, полученный на предыдущем этапе вместе с URL-адресом перенаправления, идентификатором клиента, секретным кодом, полученным в процессе регистрации клиента, и параметром grant_type, который должен быть установлен как authorization_code.


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

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

При этом в заголовке Authorization запроса прилагается маркер доступа. В качестве примера CURL-запроса на получение в блог данных с Google Blogger API с учетом идентификатора может послужить следующий код:

$ curl https://www.googleapis.com/blogger/v3/blogs/5223788876950011016 -H ‘Authorization: OAuth ya29.AHES6ZRTj1GNxAby81Es-p_YPWWNBAFRvBYVsYj2HZJfJHU’

Обратите внимание, что маркер доступа добавлен в качестве заголовка Authorization в запросе, а также выделен одинарными кавычками, так как маркер может содержать специальные символы.

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


Затем сервер ресурса проверяет передаваемые данные (маркер доступа) и в случае успешной проверки, отправляет запрашиваемую информацию.
Данные примеры иллюстрируют принципы работы OAuth2.0 Playground. Подобным образом, как правило, реализуется взаимодействие данной версии с Google.

Немного по-другому взаимодействие может происходить при обращении к другим сервисам (к примеру, Facebook или Salesforce), что связано с проблемами слабой совместимости конечных решений, которые мы обсудим чуть позже.

Обновление маркера доступа

Хотя это и не предусмотрено по умолчанию, но, как правило, маркеры доступа имеют ограниченный срок действия. Поэтому, когда период действия маркера истек, клиент отсылает к серверу авторизации запрос на обновление маркера.

К нему прилагаются идентификатор клиента и секретный код, а также параметр grant_type в качестве refresh_token.


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

Так в чем же проблема OAuth 2.0?

Хороший (положительный момент).

Судя по скорости, с которой был выпущен OAuth 2.0, это, безусловно, шаг вперед по сравнению с его предшественником. Известны случаи, когда члены сообщества разработчиков имели некоторые затруднения при внедрении версии 1.0. OAuth 2.0 предоставляет несколько новых видов грантов, которые могут быть использованы для реализации различных задач пользователей, связанных с встроенными приложениями, однако главным плюсом этой версии фреймворка является его простота по сравнению версией 1.0.

Плохой (негативные моменты).

Неотточенные элементы версии OAuth 2.0 могут привести к тому, что большое количество решений по его внедрению будут несовместимы друг с другом.

В данной версии остаются несколько недоработанных моментов, связанных с тем, что она не может правильно определить несколько необходимых компонентов или взаимодействие с ними еще не прописано:

  • Взаимодействие: Добавление слишком большого количества обновлений и расширений привело к тому, что некоторые компоненты просто несовместимы друг с другом. Это значит, что вы не можете написать универсальный код с использованием Endpoint Discovery, и быть уверенным, что он подойдет для взаимодействия с различными сервисами.

    На самом деле вам придется писать отдельные фрагменты кода под Facebook, Google, Salesforce и так далее. Данный момент даже признан в спецификации версии;

  • Ограниченный срок действия маркеров: Данная версия фреймворка не обеспечивает бессрочный период действия всех маркеров, которые были выданы.

    Хотя в зависимости от конкретных практических решений такая возможность существует. В большинстве же случаев предоставляется краткосрочный маркер доступа с возможностью его обновления по запросу;

  • Безопасность: В данной версии всего лишь «рекомендовано» использование протоколов SSL / TLS при отправке маркеров в виде обычного текста по сети.

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

Злой…

Было перепробовано порядка 31 черновой версии фреймворка, в ходе чего также был смещен ведущий автор / разработчик Эран Хаммер, прежде чем новая версия, наконец, увидела свет. Эран настаивал на том, что в данной спецификации реализован «плохой протокол и она нежизнеспособна вследствие еще тысячи огрех».

По его словам, использование маркеров «на предъявителя» (отправка маркеров по SSL без подписи их или любой другой верификации) вместо сигнатур пользователей (или MAC-маркеров), которые использовались в OAuth 1.0 – это неудачное решение. Это, по его словам, результат предпочтения интересов предпринимателей интересам веб-сообщества.

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

Вернемся в 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-а. Не так уж и сложно, верно?

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

Хороший способ понять что-то — сделать это самому, наступив попутно на все разложенные грабли. Сейчас мы будем изобретать велосипед: попробуем представить, как бы мы решали задачу взаимодействия 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);

Ну что же, давайте теперь считать грабли (потому что шишки считать будет уже поздно).

Грабли первые: подмена адреса возврата 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-скрипт (). Это виджет, который можно вставлять на 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; $S_REQUEST_TOK = &$_SESSION; $S_ACCESS_TOK = &$_SESSION; // Путь: // 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; while ($action) { switch ($action) {

  1. Обработка отправки формы. Это не часть OAuth, а часть нашего приложения. Прежде чем обращаться к API Provider-а, мы должны получить от пользователя заказ-наряд на это действие. Вот пример такого «заказа»:

    Листинг 9: Обработка отправки формы

    // 1. Запрошена отправка формы. Определяем, с какого шага начинать: // либо с OAuth, либо с отправки сообщения через API. case ‘form_is_sent’: { // Сохраняем сообщение в сессию, оно нам понадобится позже. $S_MSG = $_POST; 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, $parsed); // Переходим к следующему состоянию. $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}{$_SERVER}» . «?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, $parsed); // Переход к отправке сообщения. $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}»); 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)?>?action=form_is_sent» style=»margin: 1em 0 0 0″> <input type=»text» size=»30″ name=»msg» /> <input type=»submit» value=»Отправить» /> </form> </div>