2010/08/18(水)Pure PerlでのOAuth実装メモ
adiaryに、twitter投稿のためのOAuthを実装したときのメモです。PurePerl動作。
OAuthモジュール(OAuth::Lite::Consumer)を使わずに実装したので、同じことをやりたい人には参考になるかと思います。*1
資料とか
OAuthの流れ
一番最初に読むべきはRFC5849の元Draftの1.2例の項目です。とりあえず全体の流れが分からないと実装に手こずります。
twitterを前提として話をすると次のようになります。
- http://twitter.com/oauth_clientsにアクセスし、これから製作するアプリケーションを登録する。
- これにより「Consumer key(クライアント識別子)」および「Consumer secret(クライアント秘密鍵)」を得ます。
- クライアントソフトはユーザーからの要求があったとき、http://twitter.com/oauth/request_tokenへアクセスし、リクエストトークン(テンポラリクレデンシャル)及びリクエスト秘密鍵を得ます。
- クライアントソフトはユーザーに対しhttp://twitter.com/oauth/authorize?oauth_token=(リクエストトークン)へのアクセスを促し(もしくはリダイレクトをして)、アプリケーションからアカウントへのアクセスを許可させます。
- このとき、ユーザーのブラウザには数桁の暗証番号(verifier)が表示され、これをアプリケーション側に入力させます。
- リクエストトークン(テンポラリクレデンシャル)を使用しverifierと共にアクセストークンと秘密鍵*2を得るためhttp://twitter.com/oauth/access_tokenに対しリクエストを送信します。
- リクエストトークンおよびVerifierの正当性が確認されると、アクセストークン(トークンクレデンシャル)およびその秘密鍵が返されます。
以後は、得られたアクセストークン(トークンクレデンシャル)と秘密鍵により、自在にアクセスが可能になります。
この手順では暗証番号をユーザーに入力させる必要がありましたが、callbackによりこの手順を自動化することもできます。その場合リクエストトークン(テンポラリクレデンシャル)の要求を送る際にcallback URLを含めて送信します。こうすると、ユーザーがtwitter上にリクエストを許可した段階でcallback URLが呼び出され自動的に暗証番号(Verifier)を得ることができます。*3
リクエストトークンの取得
アクセストークンを得るまでの仮のトークンです。「Consumer key」および「Consumer secret」が正しければ得ることができます。例えば次のようなHTTPリクエストを送ります。
POST /oauth/request_token HTTP/1.0 Host: twitter.com Content-Type: application/x-www-form-urlencoded Content-Length: 0 Authorization: OAuth realm="", oauth_consumer_key="fqBn4Wmq2x3KyZUjPWYeNA", oauth_nonce="5PGfGBKqzkprkqh4g8K", oauth_signature="O0dNknEXA1rpCR5fSRhk18gd9AI%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1200102857", oauth_version="1.0"
ヘッダの内容 | 意味 |
---|---|
realm | ユーザー名*4 |
oauth_consumer_key | クランアントソフトの識別子 |
oauth_nonce | セッションごとにクライアントが任意生成するランダムな文字列 |
oauth_signature | リクエストの正当性を確認するための署名 |
oauth_signature_method | 署名の方法 |
oauth_timestamp | 現在の時刻(UTC) |
oauth_version | OAuthのVersion(なくても大丈夫) |
リクエストが正しく確認されれば、次のような返答があります。
HTTP/1.1 200 OK Date: Wed, 18 Aug 2010 13:40:58 GMT Server: hi Status: 200 OK Last-Modified: Wed, 18 Aug 2010 13:40:58 GMT X-Runtime: 0.01619 Content-Type: text/html; charset=utf-8 Content-Length: 145 Pragma: no-cache X-Revision: DEV Connection: close oauth_token=aVxZsxVqtUA6PIZs6g442wlRE1IC4X8dZ4Cckd8NpM8& oauth_token_secret=QYxVG7U9ISXpxBWibVOgtgbh0SZel0Op1Z3wt79I& oauth_callback_confirmed=true ※実際には改行なしの1行
リクエストトークンでは成功時に必ず「oauth_callback_confirmed=true」が付いています。このときの oauth_token および oauth_token_secret がそれぞれリクエストトークンと秘密鍵になります。
HMAC-SHA1署名
クライアントソフトがtwitterに対してリクエストを送る際、HMAC-SHA1という署名が必要になります。OAuthにはいくつかの署名方法がありますが、HMAC-SHA1が現在のところ一般的なようです。署名はbase64エンコードする必要があります。
実装例はこの記事を参照。中身は以下のように利用出来る署名ルーチンです。
my $sig = &hmac_sha1($key, $msg);
このときの$keyと$msgについて解説します。
$keyは署名用の秘密鍵で consumer_secret と token_secret を & で連結したものです。
例えば「consumer_secret=AAA」「token_secret=BBB」ならば
$key = "AAA&BBB";
となります。もし、リクエストトークン要求のようにその時点でtoken_secretがない場合は、
$key = "AAA&";
となります。"&"が付いていることに注意してください。
署名するメッセージの生成
厄介なのはメッセージの方です。
- HTTPのメソッド(GET もしくは POST)
- リクエストするパス(/oauth/request_token 等)
- Authorizationの署名およびrealmを除く中身を連結した文字列
のそれぞれを & で連結したものが署名になります。
特に厄介なのは3です。先程のリクエストトークン要求を例に説明します。
Authorization: OAuth realm="", oauth_consumer_key="fqBn4Wmq2x3KyZUjPWYeNA", oauth_nonce="5PGfGBKqzkprkqh4g8K", oauth_signature="YLR5D8gkmPc5KxDuspxiWoibUd8%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1200102857", oauth_version="1.0"
この場合のAuthorizationの連結文字列は次のようになります。
oauth_consumer_key=fqBn4Wmq2x3KyZUjPWYeNA&oauth_nonce=5PGfGBKqzkprkqh4g8K& oauth_signature_method=HMAC-SHA1&oauth_timestamp=1200102857&oauth_version=1.0 ※実際には改行なしの1行
oauth_xxxは必ずアルファベット順に並べます(realmおよびoauth_signatureを除く全てを列挙する)。*5
1~3の文字列が生成できたら、それぞれをURIエンコードしてから & で連結します。間違いがないようにコードで書いておきます。
my $msg1 = 'POST';
my $msg2 = 'http://twitter.com/oauth/request_token';
my $msg3 = "oauth_consumer_key=fqBn4Wmq2x3KyZUjPWYeNA"
. "&oauth_nonce=5PGfGBKqzkprkqh4g8K"
. "&oauth_signature_method=HMAC-SHA1"
. "&oauth_timestamp=1200102857"
. "&oauth_version=1.0";
&uri_encode($msg1, $msg2, $msg3);
my $msg = "$msg1&$msg2&$msg3";
my $sig = &hmac_sha1("consumer_secret&", $msg); # http://adiary.blog.abk.nu/0274
sub uri_encode() {
foreach(@_) {
$_ =~ s|([^\w\-\.\~])|
my $x = '%' . unpack('H2', $1);
$x =~ tr/a-f/A-F/; #重要!!
$x;
|eg;
}
}
URIエンコードするとき除外する文字列に気をつけてください。また、URIエンコード後の文字は必ず大文字にすることに注意してください。
このルーチンから次の文字列が得られます。
【署名する文字列/$msg】POST&http%3A%2F%2Ftwitter.com%2Foauth%2Frequest_token &oauth_consumer_key%3DfqBn4Wmq2x3KyZUjPWYeNA%26oauth_nonce%3D5PGfGBKqzkprkqh4g8K %26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1200102857 %26oauth_version%3D1.0 【署名文字列】YLR5D8gkmPc5KxDuspxiWoibUd8=
こうして得られた署名を oauth_signature として Authorization ヘッダに付けるのですが、署名を1度URIエンコードするのを忘れないようにしてください*6。
...,oauth_signature="YLR5D8gkmPc5KxDuspxiWoibUd8%3D",...
callbackを得る場合
- Authorization に oauth_callback="(コールバックURLをURIエンコードしたもの)" を含めます。
- 署名生成時に「oauth_callback=(コールバックURLをURIエンコードしたもの)」を含めます。
アクセス許可の取得
ユーザー(UA=ブラウザ)に対して、http://twitter.com/oauth/authorize?oauth_token=(リクエストトークン)へのアクセスを促します。もしくはリダイレクトしても構いません。
この処理では署名は一切不要です。
ユーザーに表示文字列を入力させるか、コールバックを得る方法でverifier文字列を得ます。twitterの場合7桁の数字です。
(コールバックを受ける場合の例) http://callback.example.com/callback? oauth_token=hh5s93j4hdidpola&oauth_verifier=1234567
アクセストークンの取得
twitter APIにアクセスするために必要なアクセストークンを取得します。
POST /oauth/access_token HTTP/1.0 Host: twitter.com Content-Type: application/x-www-form-urlencoded Content-Length: 0 Authorization: OAuth realm="", oauth_consumer_key="fqBn4Wmq2x3KyZUjPWYeNA", oauth_nonce="qSXhhvyBb4C7jmPqGScD", oauth_signature="azAUM6sWZLj91pPVjsWhLdN8Cxc%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1210102857", oauth_token="aVxZsxVqtUA6PIZs6g442wlRE1IC4X8dZ4Cckd8NpM8", oauth_verifier="8102799", oauth_version="1.0"
oauth_verifier="8102799"が増えています。署名は次のように生成します。
【リクエスト先】http://twitter.com/oauth/access_token 【署名鍵】fqBn4Wmq2x3KyZUjPWYeNA&QYxVG7U9ISXpxBWibVOgtgbh0SZel0Op1Z3wt79I ※consumer_key&リクエストトークンkey 【署名する文字列】(1)(2)(3)をURIエンコードして&連結したもの (1)POST (2)http://twitter.com/oauth/access_token (3)oauth_consumer_key=fqBn4Wmq2x3KyZUjPWYeNA &oauth_nonce=qSXhhvyBb4C7jmPqGScD &oauth_signature_method=HMAC-SHA1 &oauth_timestamp=1210102857 &oauth_token=aVxZsxVqtUA6PIZs6g442wlRE1IC4X8dZ4Cckd8NpM8 &oauth_verifier=8102799 &oauth_version=1.0 ※実際には改行なしの1行
レスポンス。
HTTP/1.1 200 OK Date: Wed, 18 Aug 2010 16:22:13 GMT Server: hi Status: 200 OK Last-Modified: Wed, 18 Aug 2010 16:22:12 GMT Content-Type: text/html; charset=utf-8 Content-Length: 164 X-Revision: DEV X-Frame-Options: deny Vary: Accept-Encoding Connection: close oauth_token=1234567xx-L1tWgd2WwjBiD4WUWDCix1MqFuQDFIDiG7vRGA50& oauth_token_secret=ef2f74Da01XfNczBZy57jnWzbr6vX2H1BZ22JQf3iuQ& user_id=1234567xx&screen_name=nabe_abk ※実際には改行なしの1行
アクセストークンを得れば、リクエストトークン(テンポラリクレデンシャル)およびその秘密鍵は不要になります。
screen_nameは表示名ではなく、いわゆるtwitter上のユーザーIDに相当します。
twitter APIへのアクセス(TLへの発言)
twitter API日本語訳を参考に、TLの発言の例だけ示します。
http://twitter.com/statuses/update.xmlhttp://twitter.com/statuses/update.json
twitter側の仕様変更によりURLが変更になっています(2012/10)。
- http(s)://api.twitter.com/1/statuses/update.xml
- http(s)://api.twitter.com/1/statuses/update.json
結果を受け取る形式により「.xml」もしくは「.json」に対しリクエストを送ります。
発現データはフォーム形式で「status=(発言本文)」として送信します。
POST /1/statuses/update.xml HTTP/1.0 Host: api.twitter.com Content-Type: application/x-www-form-urlencoded Content-Length: 17 Authorization: OAuth realm="", oauth_consumer_key="fqBn4Wmq2x3KyZUjPWYeNA", oauth_nonce="WER546dWkjfasloE", oauth_signature="(シグネチャ)", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1220102857M", oauth_token="(アクセストークン)", oauth_version="1.0" status=test+tweet
署名するデータ。
【署名鍵】consumer_key&アクセストークンkey(秘密鍵) (1)POST (2)http://api.twitter.com/1/statuses/update.xml (3)oauth_consumer_key=fqBn4Wmq2x3KyZUjPWYeNA &oauth_nonce=WER546dWkjfasloE &oauth_signature_method=HMAC-SHA1 &oauth_timestamp=1210102857 &oauth_token=(アクセストークン) &oauth_version=1.0 &status=test%20tweet ※実際には改行なしの1行
うまく動作すれば、twitterに発言することができます。
twitterにupdateなどのデータを送る際フォームデータ(この例ではstatus)とAuthorizationヘッダの中身の両方の要素をアルファベット順に並べて署名対象メッセージを生成する必要があります。*7
フォーム要素のデータは当然ながらURIエンコードする必要があり、署名用メッセージはURIエンコードされた文字列を使用します。また日本語を使用する場合は文字コードとしてUTF-8を使用してください。
フォーム要素のURIエンコード
sub uri_encode_com { foreach(@_) { $_ =~ s|([^\w!\(\)\*\-\.\~\/:])| my $x = '%' . unpack('H2', $1); $x =~ tr/a-f/A-F/; $x; |eg; } }
スペースを「+」に置換すると失敗します。また「:」を含むことに注意してください。
一方でhttpで送る時のエンコード(postデータ)は「%2f」のように小文字でも良く、POSTされたデータを一度元に復元してから再度署名確認していると思われます。
ツッコミ
ツッコミがあればコメント/TBしてください。記事は随時修正する予定です。