OAuth
OpenIDが、あなたは本人ですか?ねぇほんと?ねぇ?的な身元証明の技術であるのに対して、OAuthはこの人に合い鍵渡していい?ねぇ?ほんとにいいの?的な私財へのアクセス許可の技術です。
現在、Twitterの他にもYahooやGoogleのAPIもOAuth認証に対応しています。FlickrもOAuthのベースになってるような認証だった気がするけど、今はどうなんでしょうね。
とりあえずベーシック認証よりは、よっぽどセキュアというわけですが、詳しいメリットや成り立ちなどは、下の参考URLでもご覧くださいまし。
参考:APIアクセス権を委譲するプロトコル、OAuthを知る − @IT
そのプロセス
- コンシューマー登録
- アパートの管理人(サービスプロバイダ ex.Twitter)に、合い鍵収集業者(コンシューマー)として登録し、業者ID ( コンシューマーキーとコンシューマーシークレット ) を得ます。
- リクエストトークンの請求
- アパートの住人(ユーザー ex.Twitterユーザー)に、合い鍵くれとリクエストを送ります。その際、業者IDをユーザーに渡して、OKなら業者IDを管理人に渡してくれ、と頼みます。
- ユーザーの認否
- ユーザーは管理人のとこに行って、この業者に合い鍵を渡すかどうかを判断します。(Twitterとの連携サービスとかをやると、良く出てくるアレです。許可・不許可の。)
- リクエストトークンの発行
- ユーザーがOKを出してくれると、合い鍵業者は、管理人に合い鍵を請求するための委任状(リクエストトークン)をもらえます。
- アクセストークンの入手
- 委任状を手に入れた合い鍵業者は、それを携えて管理人に、そのユーザーの合い鍵(アクセストークン)をもらいに行って、それを受け取ります。
- 合い鍵が変わったら?
- 合い鍵はたまに変わってしまうかもしれません。そのとき、合い鍵業者は委任状と前の鍵を持って、もう一度管理人に会いに行きます。委任状が今でも有効なら、新しい合い鍵を受け取れます。すでに無効なら、それは住人が、あなたとの縁を切ったということです。
逆にわかりづらい?
抽象化したら逆に分かりづらい気がする。そうでもないか?まあ、そんな感じでトカゲのしっぽを切れる程度の便利認証でござる。一晩だけの関係とかでもいいわけですよ。合い鍵業者とか訳の分からないことを言わずに、夜這い業者と訳した方が良かったか。
前エントリーで述べたような、HTTPリクエストの実装よりは、よっぽどハマりポイントはありません。仕様に従って作れば一通り問題ありませんでした。OAuthで重要なのは、その意義(使い所)とプロセスを理解することです。
RFC3986の形式でちゃんとエンコードしないとダメとか、そういうのはありますが、コンシューマー側を作る分には、そこまで大変ではありません。サービスプロバイダー側を実装するのは結構面倒くさそうな...。
また普通にソースコードも置いておく
ちょっと古いやつだけど。
$key = 'コンシューマーキー'; $secret = 'コンシューマーシークレット'; $oAuth = new OAuth($key, $secret); $url = 'APIへのURL'; $params = '連想配列になったパラメーター'; $http_method = 'GETとかPOSTとか'; // これでOAuth用のリクエストを生成します // アクセストークンとか受け取った後は、 // インスタンス作るときにトークン&トークンシークレットも渡す感じ $request = $oAuth->buildRequest($url, $params, 'HMAC-SHA1', $http_method);
後日Tiwtter的なラッパーサンプルも出しておきます。最後にOAuth用の本体をば。
<?php abstract class OAuth_Consumer { abstract protected function _setUrl(); abstract protected function httpRequest($url, $params = array(), $http_method = 'GET'); protected function _parseQuery($query) { if ( empty($query) ) return false; $ary = explode('&', $query); if ( !is_array($ary) ) return false; foreach ( $ary as $a ) { list($key, $val) = explode('=', $a); $parsed[$key] = $val; } return !empty($parsed) ? $parsed : false; } protected function getReqToken() { if ( !!($this->httpRequest($this->request_token_url)) ) { $token = $this->_parseQuery($this->response->body); } if ( !empty($token) ) { $this->oAuth = OAuth::RequestToken($this->oAuth, $token['oauth_token'], $token['oauth_token_secret']); return $token; } return $this->response->body; } protected function getAcsToken() { if ( !!($this->httpRequest($this->access_token_url)) ) { $token = $this->_parseQuery($this->response->body); } if ( !empty($token) ) { $this->oAuth = OAuth::AccessToken($this->oAuth, $token['oauth_token'], $token['oauth_token_secret']); return $token; } return $this->response->body; } } class OAuth { protected $version = '1.0'; protected $base; public $key; public $secret; public $token; public $token_secret; public $callback; public $scope; public $verifier; public $handler; public $display; public function __construct($key, $secret, $token = null, $token_secret = null) { $this->key = $key; $this->secret = $secret; if ( !empty($token) && !empty($token_secret) ) { $this->token = $token; $this->token_secret = $token_secret; } else { $this->token = null; $this->token_secret = null; } } static public function RequestToken($consumer, $token, $token_secret) { return new OAuth_RequestToken($consumer, $token, $token_secret); } static public function AccessToken($consumer, $token, $token_secret) { return new OAuth_AccessToken($consumer, $token, $token_secret); } public function setCallback($url) { $this->callback = $url; } public function setScope($scope) { $this->scope = $scope; } public function setVerifier($verifier) { $this->verifier = $verifier; } public function setHandler($handler) { $this->handler = $handler; } public function setDisplayName($name) { $this->display = $name; } public function mergeOAuthParams($params, $method) { $params = array_merge((array)$params, array( 'oauth_version' => $this->version, 'oauth_nonce' => md5(microtime().mt_rand()), 'oauth_timestamp' => time(), 'oauth_consumer_key' => $this->key, 'oauth_signature_method' => $method, )); if ( !empty($this->token) ) $params['oauth_token'] = $this->token; if ( !empty($this->callback) ) $params['oauth_callback'] = $this->callback; if ( !empty($this->scope) ) $params['scope'] = $this->scope; if ( !empty($this->verifier) ) $params['oauth_verifier'] = $this->verifier; if ( !empty($this->handler) ) $params['oauth_session_handle'] = $this->handler; if ( !empty($this->display) ) $params['xoauth_dispalay_name'] = $this->display; return $params; } public function buildSignature($url, $params, $method, $http_method = 'GET') { /** * build base strings */ $material = array( OAuth_Signature::rfc3986($http_method), OAuth_Signature::rfc3986($url), OAuth_Signature::rfc3986($this->_HttpBuildQuery($params)), ); $this->base = implode('&', $material); /** * detect signature method */ switch ($method) { case 'HMAC-SHA1' : $signature = OAuth_Signature::hmacSha1($this->base, $this->secret, $this->token_secret); return $signature; default: break; } } public function buildRequest($url, $params, $method, $http_method = 'GET') { $this->params = $this->mergeOAuthParams($params, $method); $signature = $this->buildSignature($url, $this->params, $method, $http_method); $this->params['oauth_signature'] = $signature; $query = $this->_HttpBuildQuery($this->params); return $url.'?'.$query; } public function _HttpBuildQuery($params) { while ( list($key, $val) = each($params) ) { $key = OAuth_Signature::rfc3986($key); $val = OAuth_Signature::rfc3986($val); $encoded[$key] = $val; } $params = $encoded; ksort($params); foreach ( $params as $key => $val ) { $queries[] = $key.'='.$val; } return implode('&', $queries); } } class OAuth_RequestToken extends OAuth { public function __construct($consumer, $token, $token_secret) { parent::__construct($consumer->key, $consumer->secret, $token, $token_secret); } } class OAuth_AccessToken extends OAuth { public function __construct($consumer, $token, $token_secret) { parent::__construct($consumer->key, $consumer->secret, $token, $token_secret); } } class OAuth_Signature { static public function hmacSha1($base, $consumer_secret, $token_secret = '') { $keys = array( OAuth_Signature::rfc3986($consumer_secret), OAuth_Signature::rfc3986($token_secret), ); $key = implode('&', $keys); return base64_encode(hash_hmac('sha1', $base, $key, true)); } static public function rfc3986($str) { return str_replace('%7E', '~', rawurlencode($str)); } static private function alias_hash_hmac() { // そのうちhash_hmac関数の代替品を組み込む } } ?>