Как стать автором
Обновить

Авторизация по протоколу OAuth на примере Desktop Twitter-клиента

Время на прочтение6 мин
Количество просмотров25K
Потребовалось мне тут написать некий кроссплатформенный Twitter-клиент с закрытым исходным кодом, не спрашивайте зачем мне это надо, работа у меня такая, деньги я за это получаю. Что логично, языком разработки был выбран С++ с использованием Qt.
Сам API Twitter'a прост как кирзовый сапог. Но! Есть такая важная штука как авторизация, и тут есть два пути, старый — аутентификация посредством HTTP Headers и новый — использование протокола OAuth. Старый метод прост, также как и само API, но, к сожалению, он не безопасен, и самое главное команда Twitter'a предупреждает, что откажется от него в конце июня сего года. Поэтому остается второй метод OAuth. Надо сказать, что данный протокол используется не только в Twitter, но поскольку я писал Twitter-клиент, и рассматривать мы будем на примере Twitter'a.

Теория


Итак, изучив Twitter API и описание OAuth протокола, я узнал следующее, для авторизации Desktop-приложения необходимо зарегистрировать ваше будущее приложение в Twitter, это можно сделать на этой странице: twitter.com/oauth_clients, вам буду выданы два ключа: oauth_consumer_key и oauth_consumer_secret. Эти ключи следует запомнить и в дальнейшем вставить в ваше приложение как константы, так как они с ним неотрывно связаны.

Теперь рассмотрим процесс авторизации по шагам:
  1. С помощью некоего GET-запроса на адрес api.twitter.com/oauth/request_token мы должны получить первоначальное значение ключей oauth_token и oauth_secret
  2. С помощью некоего GET запроса мы должны открыть в браузере пользователя следующую страницу: api.twitter.com/oauth/authorize
  3. На этой странице у пользователя спросят его логин и пароль, если он не авторизован на сайте и спросят, действительно ли он желает разрешить данному приложению доступ в Twitter от имени его аккаунта
  4. После согласия пользователя ему будет показан PIN-код
  5. Наше приложение тем временем должно предложить пользователю диалог для ввода PIN-кода
  6. После того как пользователь скопирует из браузера PIN и вставит его в наше приложение, оно должно выполнить некий POST-запрос на адрес: api.twitter.com/oauth/access_token, это необходимо для получения настоящих ключей oauth_token и oauth_secret, которые в дальнейшем будут использоваться для идентификации пользователя в системе.

Теперь подробнее про запросы, их, напоминаю три: request_token, autorize и access_token

request_token


Данный запрос должен содержать в HTTP заголовке следующее поле:
Authorization: OAuth realm=«http%3A%2F%2Fapi.twitter.com%2F»,
oauth_consumer_key=«0685bd9184jfhq22»,
oauth_signature_method=«HMAC-SHA1»,
oauth_timestamp=«137131200»,
oauth_nonce=«4572616e48616d6d65724c61686176»,
oauth_version=«1.0»,
oauth_signature=«wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D»

realm — хост на который отсылается запрос;
oauth_consumer_key — тот самый ключ, который нам выдали при регистрации;
oauth_signature_method — метод шифрования сигнатуры, их существует три для данного протокола: PLAINTEXT, HMAC-SHA1, RSA-SHA1, но опытным путем было выяснено, что Twitter не поддерживает PLAINTEXT;
oauth_timestamp — текущий timestamp на вашей машине;
oauth_nonce — случайно генерируемая для каждого запроса строка, своего рода соль;
oauth_version — версия протокола, всегда 1.0;
oauth_signature — о! этот параметр самый заковыристый, формируется он следующим образом:
Нам необходимо выполнять HMAC-SHA1 (или RSA-SHA1) с ключем, который формируется так: urlencode("<oauth_consumer_secret>&<oauth_token_secret>") — (на данном этапе oauth_token_secret еще не получен, поэтому он будет пустым) и базовой строкой, которая составляется следующим образом: "<метод запроса>&<urlencode(адрес запроса)>&<urlencode(key_sort(параметры запроса))>", чтобы было понятнее приведу пример:
GET&http3A%2F2Fapi.twitter.com%2Frequest_token&oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_version%3D1.0

и пример ключа:
kd94hf93k423kf44%26

После чего получившееся значение следует обработать алгоритмом base64.

Response этого запроса, если все прошло удачно, будет содержать строку вида:
oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00

Эти параметры надо распарсить и запомнить.

autorize


Самый простой шаг, вам нужно открыть в браузере следующий запрос:
api.twitter.com/oauth/authorize?oauth_token=nnch734d00sl2jdk

Итогом действий пользователя в браузере будет выведенный ему PIN-код.
Ваше приложение должно запросить этот PIN-код и запомнить его, он потребуется на следующем шаге.

access_token


Завершающий шаг авторизации, теперь мы должны получить настоящие ключи oauth_token и oauth_token_secret.
Для этого нужно выполнить POST запрос на адрес: api.twitter.com/oauth/access_token.
POST параметры должны содержать следующее:
oauth_consumer_key=dpf43f3p2l4k3l03
oauth_token=hh5s93j4hdidpola
oauth_signature_method=HMAC-SHA1
oauth_timestamp=1191242092&
oauth_version=1.0
oauth_nonce=dji430splmx33448
oauth_verifier=213423534
oauth_signature=kd94hf93k423kf44%26hdhd0244k9j7ao03

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

Практика


Практика показала, что нет ни одной вменяемой реализации HMAC-SHA1 на С++ под LGPL, и это грустно, я пробовал сделать что-то с этой реализацией: bit.ly/96RlAL — у меня не получилось. Есть вариант написать свою, с использованием OpenSSL, к сожалению у меня не было на это времени. Однако, во время интенсивного гугления я нашел библиотеку под названием QOAuth

QOAuth


Cчастью моему не было придела :), однако, даже с ней все оказалось не так просто.
Сама библиотека включается в проект, очень легко и просто компилируется (кстати мне не удалось заставить ее работать как отдельный *.so/*.dll файл, но это уже сказалась нехватка времени и кривизна моих рук), но у этой библиотеки есть зависимости, а именно QCA — Qt Cryptographic Architecture и ее плагин QCA OpenSSL.

QCA и QCA ossl plugin


Сборка самой QCA тривиальна, все шаги описаны в README, однако, ossl-плагин собираться отказался вылетев с таким вот сообщением:
qca-ossl.cpp: In function 'X509_EXTENSION*
opensslQCAPlugin::new_subject_key_id(X509*)':
qca-ossl.cpp:330: warning: deprecated conversion from string constant to
'char*'
qca-ossl.cpp: In member function 'virtual QCA::Provider::Context*
opensslProvider::createContext(const QString&)':
qca-ossl.cpp:6815: error: 'EVP_whirlpool' was not declared in this scope
*** Error code 1

И тут я почувствовал, что это конец :( однако, на помощь пришел хаброюзер tass и помог нагуглить решение: www.mail-archive.com/kde-freebsd@kde.org/msg03672.html — вот он патч разрешающий ошибку (обратите внимание патч для KDE под FreeBSD :) ). Ну дальше осталось самое простое, собрать все воедино и написать код запросов, код был взять из примеров QOAuth и модифицирован, потому как примеры устарели в отличие от самой библиотеки и стали «некомпилябельными», поэтому прикладываю примеры работающих request_token и access_token

request_token


  1. QOAuth::Interface *qoauth = new QOAuth::Interface(this);
  2. qoauth->setConsumerKey( consumerKey.toAscii());
  3. qoauth->setConsumerSecret( consumerSecret.toAscii() );
  4. qoauth->setRequestTimeout( 10000 );
  5.  
  6. QOAuth::ParamMap reply = qoauth->requestToken( oAuthRequestTokenUrl, QOAuth::GET, QOAuth::HMAC_SHA1 );
  7. if ( qoauth->error() == QOAuth::NoError )
  8. {
  9.   token = reply.value( QOAuth::tokenParameterName() );
  10.   tokenSecret = reply.value( QOAuth::tokenSecretParameterName() );
  11. }
* This source code was highlighted with Source Code Highlighter.


acess_token


  1. QOAuth::ParamMap otherArgs;
  2. otherArgs.insert( QByteArray(«oauth_verifier»), pin.toAscii() );
  3.  
  4. QOAuth::ParamMap reply = qoauth->accessToken( oAuthAccessTokenUrl, QOAuth::POST, token, tokenSecret, QOAuth::HMAC_SHA1, otherArgs );
  5.  
  6. if ( qoauth->error() == QOAuth::NoError ) {
  7.   token = reply.value( QOAuth::tokenParameterName() );
  8.   tokenSecret = reply.value( QOAuth::tokenSecretParameterName() );
  9.   QString userName = reply.value( «screen_name» );
  10. }
* This source code was highlighted with Source Code Highlighter.


Полезные ссылки


Twitter API
OAuth протокол

Заключение


Я как и во всех остальных моих статьях не претендую на полноту решения, и не предлагаю пошаговое руководство, я лишь описываю базовую последовательность действий необходимую для решения задачи и подводные камни, с которыми столкнулся сам. Также буду благодарен, если кто-то подскажет работающую кроссплатформенную LGPL-ную реализацию HMAC-SHA1.
Теги:
Хабы:
+29
Комментарии33

Публикации

Изменить настройки темы

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн