$shibayu36->blog;

株式会社はてなでエンジニアをしています。プログラミングや読書のことなどについて書いています。

2-legged OAuthを理解する

 APIの認証ってどうやってるんだろーって思っているときに、Google Dev Quizで2-legged OAuthの問題があったので、やりながら理解する事にした。perlで書いてみました。

OAuthのcpanモジュールを使った場合

 この場合、簡単にできます。OAuth::Lite::Consumerにconsumer_keyとconsumer_secretとrealmを渡して作成してやった後、method, url, paramsを指定してrequestするだけです。timestamp値やnonceなどはすべて自動で付けて、署名を作ってくれています。

use strict;
use warnings;

use OAuth::Lite;
use OAuth::Lite::Consumer;

my $ua = LWP::UserAgent->new;

my $request_url = 'http://gdd-2010-quiz-japan.appspot.com/oauth/hogehogehoge';

my $consumer = OAuth::Lite::Consumer->new(
    consumer_key => 'hogehoge',
    consumer_secret => 'piyopiyo',
    realm => 'devquiz',
);

my $res = $consumer->request(
    method => 'POST',
    url => $request_url,
    params => {hello => 'world'},
);

use YAML;
warn YAML::Dump($res);

OAuthモジュールを使わない場合

さすがに上のコードでは、ラップされすぎていて理解できなかったので、OAuthモジュールを使わずになんとか自分でやってみました。適当にいじっていったので、むちゃくちゃ汚いコードになっていますが、下のようになりました。

use strict;
use warnings;

use String::Random;
use DateTime;
use URI::Escape;
use Digest::HMAC_SHA1 qw(hmac_sha1);
use MIME::Base64;
use LWP::UserAgent;

my $realm = 'devquiz'; # realm
my $param = { hello => 'world' }; # 送りたいparameter
my $url = 'http://gdd-2010-quiz-japan.appspot.com/oauth/hogehogehoge'; # 送りたいurl
my $oauth_consumer_secret = 'piyopiyp'; # 秘密鍵

my $oauth_param = {
    oauth_consumer_key => 'hogehoge',
    oauth_nonce => String::Random->new->randregex('\w{20}'),
    oauth_timestamp => DateTime->now->epoch,
    oauth_version => '1.0',
    oauth_signature_method => 'HMAC-SHA1'
};


# Authorizationヘッダの作成
my $header = join(', ', map {
    $_ . '=' . '"' . $oauth_param->{$_} . '"';
} keys %$oauth_param);
$header = qq{OAuth realm="${realm}", } . $header;

# signatureの為のベース文字列を作成
my $signature_string = "POST&" . uri_escape($url);
my $signature_param = {
    %$oauth_param,
    %$param,
};
my $parameter_string .= join('&', map {
    $_ . '=' . $signature_param->{$_};
} sort keys %$signature_param);
$signature_string .= '&' . uri_escape($parameter_string);

# signatureの作成
my $hmac_sha1_key = $oauth_consumer_secret . '&';
$signature_string = hmac_sha1($signature_string, $hmac_sha1_key);
$signature_string = encode_base64($signature_string);
chomp $signature_string;  # なぜか改行コードが追加されてる...orz

# headerへ代入
$header .= ', oauth_signature="' . $signature_string . '"';

# 送信
my $ua = LWP::UserAgent->new;
my $res = $ua->post($url, $param, 'Authorization' => $header);

use YAML;
warn YAML::Dump($res);

2-legged OAuthの手順

 モジュールを使わずにやってみたら、手順が理解できたので、まとめておきます。

1.様々なパラメータから署名を作成する

 まず以下の三つの文字列を&で繋いだベース文字列を作成する。

  • リクエストメソッド名(GET, POSTなど)
  • リクエストURLをuri_escapeしたもの(GETの場合パラメータは除く)
  • consumer_secretとrealm以外のOAuthパラメータと送信したいパラメータを、キー名=値という形にして&でつなげ、uri_escapeしたもの。ただし、キー名でソートした順番になっていなければならない。
    • 今回だったら次のようになります。「hello%3Dworld%26oauth_consumer_key%3Dhogehoge%26oauth_nonce%3DRJQX3sK5nIodUcRS426k%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1282447259%26oauth_version%3D1.0」

 次にconsumer_secretとtokenを&で繋いだものをキーとして、hmac_sha1でダイジェスト値を作ります。今回だったらtokenは空なので「piyopiyo&」のような文字列をキーとします。

 最後にダイジェスト値をbase64エンコードします。これで署名は完成します。

2.Authorizationヘッダを作成する

 oauthに必要な情報を「OAuth キー名="値", キー名="値", ...」というように繋げた文字列を作ります。今回であれば必要な情報は下の通りです。

  • oauth_consumer_key
  • oauth_nonce
  • oauth_timestamp
  • oauth_version
  • oauth_signature_method
  • realm
  • oauth_signature

ヘッダとしては以下のようになりました。

OAuth realm="devquiz", oauth_timestamp="1282447809", oauth_nonce="nVj2Up8xkA2fJkyGLE4X", oauth_consumer_key="hogehoge", oauth_version="1.0", oauth_signature_method="HMAC-SHA1", oauth_signature="lKWlM2ZnyCAmCDOSIMZGSyNB4Nc="
3.送信

 1,2で必要な情報は揃ったので、後はこれらを添えて普通にリクエストするだけです。perlだったらLWP::UserAgentを使って出来ます。

まとめ

 cpanのモジュールを使うと、いろいろな事が簡単に出来てしまうんですが、たまには仕組みを理解する為に車輪の再発明をしてみるのもよさそうですね。またたまにはやってみようと思います。