素のphpでOAuthする

概要
なるべく素のphpでOAuthしてみる。
これで他のライブラリ等に依存しなくても、twitterのAPIや、tumblerのAPIが使える。
アクセストークンを取得するまでの流れはまた別な機会に。アクセストークンが取得済みの状況で、APIを使うところをやってみる。
phpの5.4ならば、やりやすい条件はそろっている。
- php5.4では、rawurlencode()関数がRFC 3986方式になっている。
- php5.4では、http_build_query()関数がRFC 3986に対応した(オプションで指定すれば)。
20行目までは必要な関数の準備。メインの処理がその後に。
サンプル スクリプト
<?php
//ライブラリは要らないけど、これだけは使いたい
//OAuth式 パラメータのソート用比較関数
function oauthcmp($a, $b)
{
return strcmp($a[0], $b[0])
? strcmp(rawurlencode($a[0]), rawurlencode($b[0]))
: strcmp(rawurlencode($a[1]), rawurlencode($b[1]));
}
//OAuth式 パラメータのソート関数
function oauthsort($a)
{
$b = array_map(null, array_keys($a), $a);
usort($b, 'oauthcmp');
$c = array();
foreach ($b as $v) {
$c[$v[0]] = $v[1];
}
return $c;
}
//OAuthのいつもの
$consumer_key = '**********************';
$consumer_secret = '****************************************';
$oauth_token = '********-*****************************************';
$oauth_token_secret = '*****************************************';
//どの twitter API
$url = 'https://api.twitter.com/1/statuses/update.json';
//POST or GET
$method = 'POST';
//パラメータ
$parameters = array('status' => 'テストのツイート');
$oauth_parameters = array(
'oauth_version' => '1.0',
'oauth_nonce' => microtime(),
'oauth_timestamp' => time(),
'oauth_consumer_key' => $consumer_key,
'oauth_token' => $oauth_token,
'oauth_signature_method' => 'HMAC-SHA1',
);
//署名を作る
//パラメータを仕様のとおりに並び替える(これが少々手間)
$base_string = implode('&', array(
rawurlencode($method),
rawurlencode($url),
rawurlencode(http_build_query(oauthsort(array_merge($oauth_parameters, $parameters)), '', '&', PHP_QUERY_RFC3986))
));
$key = implode('&', array(rawurlencode($consumer_secret), rawurlencode($oauth_token_secret)));
$oauth_parameters['oauth_signature'] = base64_encode(hash_hmac('sha1', $base_string, $key, true));
//POSTリクエストはfile_get_contents()で十分可能
$options = array(
'http'=>array(
'method' => $method,
'header' => array(
'Authorization: OAuth ' . http_build_query($oauth_parameters, '', ',', PHP_QUERY_RFC3986),
),
'content' => http_build_query($parameters)
)
);
$result = file_get_contents($url, false, stream_context_create($options));
//結果を確認してみる
var_dump($result);
注意など
oauthcmpでなにやら複雑な比較をしているのは、パラメータをキーの順で並び替える部分で、キーが同じだったら値の順で並び替えることになっているため。
そのように作ってみたものの、パラメータをphpの連想配列で表現していたら、キーを重複させたくても重複できない。
また、一般にパラメータに配列を受け取るようなAPIが(たぶん)ないと思うので、あまり問題にならないと思う。
問題が発生したらまたそのときに。。。
追記: GETリクエストも考慮した
上の例ではGET渡しするパラメータを考えていなかった。GET渡しするパラメータも取り扱うようにすると、こんな感じ。
<?php
//OAuthのいつもの
$consumer_key = '**********************';
$consumer_secret = '****************************************';
$oauth_token = '**************************************************';
$oauth_token_secret = '*****************************************';
//どのAPI?(URL)
$url = 'https://api.twitter.com/1.1/search/tweets.json';
//POST or GET
$method = 'GET';
//パラメータ
$post_parameters = array(
);
$get_parameters = array(
'q' => 'あああ',
'lang' => 'ja',
);
$oauth_parameters = array(
'oauth_consumer_key' => $consumer_key,
'oauth_nonce' => microtime(),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_timestamp' => time(),
'oauth_token' => $oauth_token,
'oauth_version' => '1.0',
);
//署名を作る
//署名の計算にはoauth_*とAPIのパラメータを含めて全部使う
//GETリクエストのときでもURLにはクエリ文字列を含めない
//キーと値でソートする仕様なのだが、キーが重複したり英数字以外が混じったりしないのでksort関数で十分
$a = array_merge($oauth_parameters, $post_parameters, $get_parameters);
ksort($a);
$base_string = implode('&', array(
rawurlencode($method),
rawurlencode($url),
rawurlencode(http_build_query($a, '', '&', PHP_QUERY_RFC3986))
));
$key = implode('&', array(rawurlencode($consumer_secret), rawurlencode($oauth_token_secret)));
$oauth_parameters['oauth_signature'] = base64_encode(hash_hmac('sha1', $base_string, $key, true));
//APIにリクエスト送信
$options = array(
'http'=>array(
'method' => $method,
'header' => array(
'Authorization: OAuth ' . http_build_query($oauth_parameters, '', ',', PHP_QUERY_RFC3986),
),
'content' => http_build_query($post_parameters)
)
);
$result = file_get_contents($url . ($get_parameters ? '?' . http_build_query($get_parameters) : ''), false, stream_context_create($options));
//結果を確認してみる
var_dump($result);