OAuthについて(OpenIDとは違うのだよ)
Posted: Updated:
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関数の代替品を組み込む
}
}
?>