「ゼロから学ぶOAuth」のサンプルをUbuntu9.10で試したときのメモとOAuhプロトコルの詳細について

OAuthの勉強中です。日本語の説明だとゼロから学ぶOAuthが丁寧でわかりやすかったです。
自分でもサンプルを動かして見ましたが、Ubuntu9.10の環境だといくつか手順が必要だったのでそれをメモしておきます。
それと、記事自体には通信のトレースがすべて載っていなかったので、Consumer Key、Consumer Secretを使った3 legged Oauthの通信について一通り載せて、本家への参照も含めて解説をつけてみました。

サンプルアプリケーションを動かす

Ubuntu9.10で普通にrailsをインストールすると2.3.5が入るので、一つ前のrailsを入れる必要があります。

$gem install -v=2.3.4 rails

あと、db:migrateでライブラリが足りないエラーが出ました。

$ rake db:migrate --trace
...中略...
rake aborted!
no such file to load -- net/https
/home/hrendoh/.gem/ruby/1.8/gems/rails-2.3.4/lib/initializer.rb:271:in `require_frameworks'
...以下略...

libopenssl-rubyを入れれば解決します。

$sudo apt-get install libopenssl-ruby

後は、記事のとおりで問題なくサンプルアプリケーションのセットアップは終了です。

あと、tcpflowをインストールしておきます。

$sudo apt-get install tcpflow

では実際にサンプルを動かして、一通りの通信キャプチャして置きます。
tcpflowをインストールして、起動しておきます。

$sudo apt-get install tcpflow
$sudo tcpflow -i eth0 -c

今回サンプルは、twitterで試しました。以下、通信内容について説明。

リクエストークンの取得

twitterの場合は、「http://twitter.com/oauth/request_token」に対してリクエストを投げます。

 POST /oauth/request_token HTTP/1.1
Accept: */*
Connection: close
User-Agent: OAuth gem v0.4.0
Content-Type: application/x-www-form-urlencoded
Authorization: OAuth oauth_callback="http%3A%2F%2Flocalhost%3A3000%2Foauth%2Ftwitter%2Fcallback", oauth_consumer_key="QwvCxMupZsztCNB6BTigg", oauth_nonce="cKXne6v4YF6KBc0WwfB5nLI9Zioo43EBFHeucBGU", oauth_signature="jpSOTIqlNEHrDQSt4Xw8FqhX638%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1273216539", oauth_version="1.0"
Content-Length: 0
Host: twitter.com

サンプルで利用しているRuby OAuth GEMのOAuth::ConsumerクラスはデフォルトではPOSTでリクエストを実行します。
twitterのrequest_token取得はGETでOKだった気がしますが、どっちでも受け付けるみたいです。

Request Tokenの取得で重要なのはAuthorizationヘッダの内容です。
詳しくは、本家の「6.1. Obtaining an Unauthorized Request Token」に書かれています。
ここでも、簡単に各パラメータについて説明します。

oauth_consumer_key

Consumer Keyを指定します。

oauth_signature_method

次のoauth_signatureの値を生成するアルゴリズムをしています。twitterは「HMAC-SHA1」のみサポートしています。

oauth_signature

著名の生成方法は本家の「http://oauth.net/core/1.0a/#signing_process:title=9.1. Signature Base String」に、例が「Appendix A.5. Accessing Protected Resources」にあります。
ハッシュされる文字列は以下の項目を&でつなげます。

  1. リクエストメソッド、必ず大文字。サンプルでは「POST」を指定。
  2. リクエストークンを取得するURL、パラメータとデフォルトポートは取り除いたURLをエンコードしたもの。twitterのリクエストークン取得用のURL「http://twitter.com/oauth/request_token」をURLエンコードしたもの「http%3A%2F%2Ftwitter.com%2Foauth%2Frequest_token」を指定
  3. Authorizationヘッダのrealmとoauth_signatureを除いたパラメータと、GETのパラメータまたはPOSTのリクエストボディ(application/x-www-form-urlencoded)をソートして"&"で全部連結してエンコードした文字列。twitterの場合はAuthorizationヘッダの内容だけ含めます。

上記をまとめると以下のような文字列になります。

POST&http%3A%2F%2Ftwitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttp%253A%252F%252Flocalhost%253A3000%252Foauth%252Ftwitter%252Fcallback%26oauth_consumer_key%3DQwvCxMupZsztCNB6BTigg%26oauth_nonce%3DcKXne6v4YF6KBc0WwfB5nLI9Zioo43EBFHeucBGU%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1273216539%26oauth_version%3D1.0

ハッシュのキーはconsumer_secretとsecret tokenを"&"で連結したものになります。Secret Tokenはまだ取得していないので空のままで「5Jyyw4BRadq2leA7bQ5wqthckxXIUh62mK3yelk8Oo4&」になります。
HMAC-SHA1でハッシュしてbase64エンコードすると、確かに「jpSOTIqlNEHrDQSt4Xw8FqhX638=」になります。

oauth_timestamp

リクエスト時のタイムスタンプ

oauth_nonce

リクエストを一意に識別するためのランダム文字列

oauth_version

今のところ1.0しかないので「1.0」を指定します。

oauth_callback

ユーザの認可プロセスの後、コールバックされるのURL(絶対URL)を指定します。

拡張プロパティ

サービスプロバイダが定義している場合にのみ指定します。

上記の処理は、実際にはライブラリが上記の処理をやってくれるので実際には簡単です。

twitterからのレスポンスは以下の通り

HTTP/1.1 200 OK
Date: Fri, 07 May 2010 07:15:40 GMT
Server: hi
Status: 200 OK
X-Transaction: 1273216540-78368-12436
ETag: "e638e712cb5670c932b5dcbf6cac27d8"
Last-Modified: Fri, 07 May 2010 07:15:40 GMT
X-Runtime: 0.01365
Content-Type: text/html; charset=utf-8
Content-Length: 147
...中略...

oauth_token=eF3CkFCGjHxdQW77sTID1eYXrIj3No0kuv5CuUbD2bw&oauth_token_secret=IThdlEEvHRWDlRbx6zav6Zam5Xnf2wjWBb7i84ZC9s&oauth_callback_confirmed=true

oauth_tokenとoauth_token_secretがレスポンスボディ含まれていることが確認できます。
oauth_callback_confirmedは、サービスプロバイダがコールバックの値を受け取ったことを確認するフラグです。

認可の処理

リクエストークンを取得した後は、TwitterのAuthorization ページ「http://twitter.com/oauth/authorize」にリダイレクトします。
そのとき、取得したoauth_tokenをパラメータに渡しています。

GET /oauth/authorize?oauth_token=eHoZfAGDrwTMOg3JA1FKgm11zEqq1yRLqTEXjsOl2ow HTTP/1.1
Host: twitter.com
User-Agent: Mozilla/5.0 (X11; U; Linux i686; ja; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:3000/oauth_access_tokens/new
Cookie: k=118.109.37.193.1273213822457314; guest_id=127321382247235591; 
...以下略...

Authorization URLはGETアクセスになります。
仕様的には、パラメータは以下のものが指定できるようです。

  • oauth_token:前の手順で取得したリクエストークンをしています。必須かどうかはサービスプロバイダーしだい。Twtterは必須?

指定しない場合は、ユーザにコンシューマを選択させるページが出てくるなどサービスプロバイダーの実装依存らしいです。

  • 追加パラメータ:サービスプロバイダーの追加パラメータ

Twitterの場合は、以下の認可のページが表示されます。

サービスコンシューマにリダイレクト

認可処理の後Twitter(サービスプロバイダー)はoauth_callbackに指定したページにリダイレクトします。

http://localhost:3000/oauth/twitter/callback?oauth_token=XokKLV51dDcro5RsfbD7EmWq4JlnIQCseeyON3oJTU&oauth_verifier=8UDFkwyliMuhAej6I3CqKaGWMayeprd3vUi7rY1xmUc

コールバックされるURLには、パラメータoauth_token(認可ページに渡した値と同じ)とoauth_verifierが追加されます。6.2.3. Service Provider Directs the User Back to the Consumer
oauth_verifierの値は、次のアクセストークンの取得時で使います。

アクセストークンの取得

Twitterからリダイレクトされてきたら、実際にデータを取得するために使うアクセストークンを取得します。
アクセストークンの取得はTwitterの場合は「http://twitter.com/oauth/access_token」にリクエストします。

POST /oauth/access_token HTTP/1.1
Accept: */*
Connection: close
User-Agent: OAuth gem v0.4.0
Authorization: OAuth oauth_body_hash="2jmj7l5rSw0yVb%2FvlWAYkK%2FYBwk%3D", oauth_consumer_key="QwvCxMupZsztCNB6BTigg", oauth_nonce="MUtMyu7plWjvN55dVLR6IUL6jlYIWlOjyX8nDTmEbA", oauth_signature="3w8dkE8xQbUrg86NG83BPkLv0ng%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1273216548", oauth_token="RNgtclAx0rxKXxzwdNcFJJzcaKiW5AvHhAJMJQ5I4U", oauth_verifier="JgKcB1D60lFYm5nUitEvJmwPZto1adx5luUFEXKHg", oauth_version="1.0"
Content-Length: 0
Host: twitter.com

リクエストークンの取得と同様にAuthorizationヘッダには、決められたパラメータを指定する必要があります。
ただoauth_verifier以外は、同じですので説明は省略します。
oauth_verifierは、コールバックされる際にパラメータに付加されてきた値を指定します。
また、リクエストークンの取得の際にsecret_tokenを取得しているので、ハッシュのキーに追加します。

HTTP/1.1 200 OK
Date: Fri, 07 May 2010 07:15:48 GMT
Server: hi
Status: 200 OK
X-Transaction: 1273216548-71383-4937
ETag: "a8b81b16d65f80679b89b0a777a3031a"
Last-Modified: Fri, 07 May 2010 07:15:48 GMT
X-Runtime: 0.02295
Content-Type: text/html; charset=utf-8
Content-Length: 162
Pragma: no-cache
X-Revision: DEV
Expires: Tue, 31 Mar 1981 05:00:00 GMT
Cache-Control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0
Set-Cookie: k=118.109.37.193.1273216548784786; path=/; expires=Fri, 14-May-10 07:15:48 GMT; domain=.twitter.com
Set-Cookie: guest_id=127321654878970392; path=/; expires=Sun, 06 Jun 2010 07:15:48 GMT
Set-Cookie: _twitter_sess=BAh7CToPY3JlYXRlZF9hdGwrCLW%252FnnEoAToRdHJhbnNfcHJvbXB0MDoHaWQi%250AJTY2YmIzYzEzYzNjODMyYTVkNDdmMTM1ODA2MjAyZThkIgpmbGFzaElDOidB%250AY3Rpb25Db250cm9sbGVyOjpGbGFzaDo6Rmxhc2hIYXNoewAGOgpAdXNlZHsA--e91c921b41ceed45cc5b11b4f2f0821034dca09c; domain=.twitter.com; path=/
Vary: Accept-Encoding
Connection: close

oauth_token=26410135-WavDm9AQHiILT9sIyHukDTTgQVmasdmVpiIHXYbqI&oauth_token_secret=efd5PNmzNPLmcUUBBFkk5J4a5jkz6mz9LQhd6TI9XEI&user_id=26410175&screen_name=hrendoh

レスポンスボディに、アクセストークンとトークンシークレットが含まれています。
とりあえず、あとは取得したアクセストークンとトークンシークレットで情報の取得の手順がありますが、著名の作成については、リクエストークンの手順と同じなので後は本家の「7. Accessing Protected Resources」を見てみてください。

参考にさせていただいたサイト

http://techno-st.net/2009/11/26/twitter-api-oauth-0.html
http://sayama-yuki.cocolog-nifty.com/blog/2009/09/twitteroauth-d7.html

あとがき

OAuth周りの実装をする場合、実際にはConsumer Key & Secretの仕組みとPublic Certificatesの仕組みを両方対応しないとならないし、今回の説明は3-legged OAuthなので、2-legged OAuthも実装しないとならないのでそれなりに大変かも。
それと、TwitterのConsumer KeyとSecretはほぼ実際に使ったものが載せてありますが、リセット済みですのでご心配なく。