とあるボットを作ろうと思った所、いくつかのサイトのファイルを保存しなければならなかったりなど、「これは面倒臭いぞ」、という訳で単体のファイルで動く、簡易的な Twitter OAuth認証 をこなせるライブラリを作成しました。
新版の Eduwitter を公開しました
詳しくは タグ:Eduwitter から
1 - 概要
1.1 - 実際に設置しました
1.2 - リファレンス(興味のある方はどうぞ)
2 - OAuthの流れ
2.1 - 基本的な単語
2.2 - 4つのステップ
2.2.1 - サービスプロバイダに外部アプリを申請する
2.2.2 - リクエストトークンの発行と承認
2.2.3 - アクセストークンの取得
2.2.4 - OAuthでAPIを叩く
2.3 - signature
3 - コード
3.1 - Eduwitter メソッド一覧
3.2 - Eduwitter での作業
3.3 - Eduwitter
1 - 概要
1.1 - 実際に設置しましたこちら Eduwitter Callback Page で動作確認できます。 リンク先に zip と tar.gz で公開しています。 同梱している eduwitter.php と eduwitter_test.php のうち、コールバックURLは eduwitter_test.php を指定してください。
1.2 - リファレンス(興味のある方はどうぞ)
OAuth 公式リファレンス
http://oauth.net/core/1.0a/
OAuth 公式リファレンス日本語訳 (twitterで紹介していただいた)
http://openid-foundation-japan.github.com/draft-hammer-oauth-10.html
OAuth 公式リファレンスの手順 Image
http://oauth.net/core/diagram.png
OAuthの手順 Flash (kuraさんの放送でリスナーさんが貼ってくれたページ)
http://labs.unoh.net/2008/02/oauth.html
Twitter API 公式
http://apiwiki.twitter.com/
Twitter API の日本語訳
http://watcher.moe-nifty.com/memo/docs/twitterAPI.txt
Python での実装、またその時に詰まった内容
http://techno-st.net/2009/11/26/twitter-api-oauth-0.html
PHP & abraham's twitteroauth での導入方法
http://www.sdn-project.net/labo/oauth.html
abraham's twitteroauth
http://github.com/abraham/twitteroauth
2.1 - 基本的な単語
OAuthで使われる基本的な単語
サービスプロバイダ … Twitterなどサービス提供側
コンシューマ … APIへアクセスするプログラム
ユーザ … このプログラムを使う人
oauth_token … リクエストトークン、またはアクセストークンが入る
oauth_token_secret … リクエストトークン、またはアクセストークンで暗号化に使うキー
oauth_verifier … クライアントモードで必要なデータとのこと、Webモードでは取得できない
便宜上このページで使う単語
リクエストトークン … ユーザリソースへのアクセス許可を求めるための一時的なトークン
アクセストークン … ユーザリソースの操作に使うトークン
OAuth で API を叩くまでの4つのステップ。
1. 外部アプリを申請する
2. リクエストトークンをユーザに承認してもらう
3. (2)を元にユーザリソースへのアクセストークンを得る
4. OAuth で API を叩く
ライブラリを組み終わってから、送信データを色々いじっていた所、送信しなくても済むパラメータがあったので、それについては省いています。
今後 API に送信すべきパラメータが標準に沿うと、このエントリーの情報では足りなくなります。
PHP のコードでは該当部分をコメントアウトしているので、どのデータが不必要か気になる方はコードを読んでください。
初めに製作者はサービスプロバイダにコンシューマの申請をします。
登録申請先: http://twitter.com/apps
申請を完了させるとサービスプロバイダから
Consumer key
Consumer secret
の二つが与えられます。
Consumer key は、サービスプロバイダがこの外部アプリと認識する為のユニークな key です。
Consumer secret は、コンシューマからの適切な送信と確認できる「暗号文の作成」時に使います。
ユーザにコンシューマを認証してもらうにはリクエストトークンが必要になります。
まずサービスプロバイダにリクエストトークンを発行してもらいます。
使用API: http://twitter.com/oauth/request_token
メソッド: GET
パラメータ
oauth_consumer_key
サービスプロバイダから割り当てられた Consumer key
oauth_signature_method
必ず HMAC-SHA1 を指定する
oauth_timestamp
time() で手に入る現在のUnixタイム(エポックタイム)
oauth_signature
メソッド、URL、またこのパラメータ以外を昇順に並べて暗号化したもの。
OAuth でも最難関なのが、この signature。
気になる方はこのページの セクション2.3 signature を参照してください。
レスポンス
oauth_token // => request_token
ユーザへのコンシューマ認証の申請に使うリクエストトークン
oauth_token_secret // => request_token_secret
リクエストトークンを使う際に利用する追加の暗号化キー
次いで、発行されたリクエストトークンをユーザに認証してもらいます(好きな方のAPIを)。
使用API1: http://twitter.com/oauth/authorize
使用API2: http://twitter.com/oauth/authenticate
メソッド: GET
パラメータ
oauth_token
先ほどのリクエストトークン(oauth_token)
レスポンス
oauth_token
先ほどのリクエストトークン(oauth_token)
authorize, authenticate の認証を済ませると、ユーザはPINを得るか指定のコールバックURLへリダイレクトされます。
このページでは PIN ではなくコールバック型で進めます。
リクエストトークンが有効になったので、ユーザリソースへのアクセストークンを貰います。
アクセストークンAPIを叩いてアクセストークンを貰う。
使用API: http://twitter.com/oauth/access_token
メソッド: GET
パラメータ
oauth_consumer_key
サービスプロバイダから割り当てられた Consumer key
oauth_signature_method
必ず HMAC-SHA1 を指定する
oauth_timestamp
time() で手に入る現在のUnixタイム(エポックタイム)
oauth_token
先ほどのリクエストトークン(oauth_token)
レスポンス
oauth_token // => access_token
外部アプリがユーザの資源を操作するためのアクセストークン
oauth_token_secret // => access_token_secret
外部アプリがユーザの資源を操作するためのデータの暗号化に使うアクセストークン
user_id
アクセストークンのユーザID
screen_name
アクセストークンのユーザ名
このうちアクセストークンAPIから貰った oauth_token と oauth_token_secret は保管しておきます。
最後に試験的にツイートを投稿します。
使用API: http://twitter.com/statuses/update.xml
メソッド: GET
パラメータ
oauth_consumer_key
サービスプロバイダから割り当てられた Consumer key
oauth_signature_method
必ず HMAC-SHA1 を指定する
oauth_timestamp
time() で手に入る現在のUnixタイム(エポックタイム)
oauth_token
アクセストークンを指定する
oauth_signature
メソッド、URL、またこのパラメータ以外を昇順に並べて暗号化したもの。
OAuth でも最難関なのが、この signature。
気になる方はこのページの セクション2.3 signature を参照してください。
追加パラメータ
status
ツイートする内容
レスポンス
XMLデータ
ツイートした結果のxmlデータ
signature の作り方については こちらのリンク を参考に。
OAuth では HMAC-SHA1 か RSA-SHA1 という方式で signature を作るように定められています(PLAINTEXTもあり)。
Twitter はこのうち HMAC-SHA1 を採用しています。
この HMAC-SHA1 で暗号化するのに「共通鍵=key」、「ハッシュ化するメッセージ」を用意します。
共通鍵=Key
コンシューマを登録した際に貰う Consumer key と、oauth_token_secret を & でつつなぎます。
oauth_token_secret が空の場合でも & は必ず付けてください。
例1:
[consumer_key]&[oauth_token_secret]
例2:
[consumer_key]&
ハッシュ化するメッセージ
メッセージは、接続時のメソッド、接続先のURL、そして送信するパラメータを使います。
パラメータは前もって、パラメータ名でソートしておきます。
メッセージは以下のようにつなげます。
抽象例:
[メソッド]&[URL]&[パラメータ]
このうち URL と パラメータは RFC3986 に則った url encode が必要
値例:
メソッド => GET
URL => http://www.twitter.com/hoge/foo
パラメータ => param1=test1&bar=barbar&n=42
ソート後のパラメータ => bar=barbar&n=42¶m1=test1
出来上がるメッセージ:
GET&http%3A%2F%2Fwww.twitter.com%2Fhoge%2Ffoo&bar%3Dbarbar%26n%3D42%26param1%3Dtest1
この key と メッセージ を元に sha1 でハッシュ化した値が signature になります。
3.1 - Eduwitter メソッド一覧
setApiPath
処理
API の Path を初期化します(クラス生成時に呼ばれる)。
引数(無し)
戻り値(無し)
__construct
処理
引数があれば、クラスに保管する
引数
consumer_key
与えられた Consumer key
consumer_secret
与えられた Consumer secret
戻り値(無し)
setRequestToken
処理
リクエストトークンをクラスに持たせる
引数
tokens
getRequestTokenで得た配列
戻り値(無し)
getRequestToken
処理
新しいリクエストトークンを取得する
引数(無し)
戻り値
リクエストトークンAPIのレスポンスを配列にしたもの
getParameter_RequestToken
処理
リクエストトークンAPIを叩くのに必要なパラメータを作成する
引数(無し)
戻り値
パラメータの文字列
setAccessToken
処理
アクセストークンをクラスに持たせる
引数
tokens
getAccessTokenで得た配列
戻り値(無し)
getAccessToken
処理
アクセストークンを取得する。
この関数を呼ぶ前に、setRequestToken を呼び出してください。
引数(無し)
戻り値
アクセストークンAPIのレスポンスをを配列にしたもの
getParameter_AccessToken
処理
アクセストークンAPIを叩くのに必要なパラメータを作成する
引数(無し)
戻り値
パラメータの文字列
requestOAuthAPI
処理
指定した OAuth API を叩く
引数
api_and_format
API の Path と取得できる文字列のフォーマット。
例:
api は statuses/update
取得フォーマットが xml
値:
statuses/update.xml
method
HTTPリクエストのメソッド
GET または POST
post_field
このAPIに送信すべきデータセット
戻り値
api_and_format で指定したフォーマットのデータ
getParameter_OAuthToken
処理
指定した OAuth API を叩くのに必要なパラメータを作成する
引数
【requestOAuthAPI】と同じ
戻り値
作成したパラメータ
Eduwitter をダウンロードして設置しておきます。
作業のステップの区分は4つのステップと同じです。
ステップ1. 外部アプリを登録した後
外部アプリを登録します。
登録後、エディタで eduwitter_test.php を開き
/*--------------------------------------------------------- Configure Section ---------------------------------------------------------*/ $consumer_key = '<Consumer key>'; $consumer_secret = '<Consumer secret>';
の各変数に
Consumer key
Consumer secret
を入れます。
Eduwitter の getRequestToken を呼ぶと、リクエストトークンを配列にして返します。
戻り値の配列は
array(
'request_token' => oauth_token,
'request_token_secret' => oauth_token_secret
);
となっています。
このうちユーザに oauth_token(リクエストトークン) を持たせたまま authoreize または authenticate のリンクへ誘導します。
ステップ3. アクセストークンを取得
ユーザがコールバックURLに戻ってきた際に、GETメソッドで oauth_token(リクエストトークン) が送られてきています。
このリクエストトークンを Eduwitter クラスに預けます。
array (
'request_token' => oauth_token,
'request_token_secret' => '' // この値はあっても無くてもよい
);
この配列を setRequestToken の引数にします。
その後 getAccessTokenメソッド を呼ぶと、アクセストークンを配列にして返します。
戻り値の配列は
array(
'access_token' => oauth_token,
'access_token_secret' => oauth_token_secret
);
となっています。
ステップ3 で取得したアクセストークンの配列を setAccessToken に放り込みます。
その後、呼び出したいAPIを requestOAuthAPI メソッドで呼びます。
コードは配布しているものと同じですが、Web上で気軽に挙動を確認したい方向けにコードを貼っておきます。
eduwitter.php<?php /********************************************************** * Eduwitter * @poochin - http://www13.atpages.jp/llan/ * LastUpdate: 2010-04-23 * License: MIT or BSD * MIT: http://www.opensource.org/licenses/mit-license.php * BSD: http://www.opensource.org/licenses/bsd-license.php * * Twitter で OAuth を勉強したい方向けのライブラリ。 * 学習用のため、重複処理を切り離さずあえて冗長にしています。 * どういう振る舞いで OAuth が使えるのか理解できたら、重複 * 処理を切り離してみると、より理解が深まるかも知れません。 *********************************************************/ define ('SCHEME_HTTP', 'http://'); define ('SCHEME_HTTPS', 'https://'); define ('HOST_TWITTER', 'twitter.com'); class Eduwitter { /* Private Area */ private $consumer_key, // provided Consumer key $consumer_secret; // provided Consumer secret private $access_token, // oauth token to access protected resource $access_token_secret; // oauth token_secret to acces protected resource private $request_token, // one-time unique oauth token $request_token_secret; // one-time unique oauth token secret private $api_path; // api_path private function setApiPath() { $this-&amp;gt;api_path = array( 'request_token' =&amp;gt; 'oauth/request_token', 'access_token' =&amp;gt; 'oauth/access_token', 'authenticate' =&amp;gt; 'oauth/authenticate', 'authorize' =&amp;gt; 'oauth/authorize', ); } /* Public Area */ /** * __construct * * Arguments * consumer_key -- provided consumer key * consumer_secret -- provided consumer secret */ public function __construct($consumer_key = null, $consumer_secret = null) { if (isset($consumer_key) &amp;amp;&amp;amp; isset($consumer_secret)) { $this-&amp;gt;consumer_key = $consumer_key; $this-&amp;gt;consumer_secret = $consumer_secret; } self::setApiPath(); } /*----------------------------------------------------- Request Token -----------------------------------------------------*/ /** * setAccessToken * * Arguments * tokens -- array include request token */ public function setRequestToken($tokens) { if (isset($tokens['oauth_token'])) { $this-&amp;gt;request_token = $tokens['oauth_token']; $this-&amp;gt;request_token_secret = $tokens['oauth_token_secret']; } else { $this-&amp;gt;request_token = $tokens['request_token']; $this-&amp;gt;request_token_secret = $tokens['request_token_secret']; } } /** * getRequestToken * * Depend on Library or Functions * Functions * getParameter_RequestToken * parameter2Array */ public function getRequestToken() { $requestTokenURL = SCHEME_HTTP.HOST_TWITTER.'/'.$this-&amp;gt;api_path['request_token']; $params = self::getParameter_RequestToken(); $response = file_get_contents($requestTokenURL.'?'.$params); return parameter2Array($response); } /** * getParameter_RequestToken * * Return * Parameter for OAuth API(request_token) * * Depend on Library or Functions * Critical Std Functions * hash_hmac * base64_encode * * Functions * array2Parameter */ public function getParameter_RequestToken() { $requestTokenURL = SCHEME_HTTP.HOST_TWITTER.'/'.$this-&amp;gt;api_path['request_token']; $parts = array ( 'oauth_consumer_key' =&amp;gt; $this-&amp;gt;consumer_key, 'oauth_signature_method' =&amp;gt; 'HMAC-SHA1', 'oauth_timestamp' =&amp;gt; time(), // 'oauth_nonce' =&amp;gt; md5(microtime() . mt_rand()), // isn't neccesary // 'oauth_version' =&amp;gt; '1.0a', // isn't neccesary ); ksort($parts); $params = array2Parameter($parts); $message = 'GET'.'&amp;amp;'. rawurlencode($requestTokenURL).'&amp;amp;'. rawurlencode($params); $key = $this-&amp;gt;consumer_secret.'&amp;amp;'; $parts['oauth_signature'] = rawurlencode(base64_encode(hash_hmac('sha1', $message, $key, true))); return array2Parameter($parts); } /*----------------------------------------------------- Access Token -----------------------------------------------------*/ /** * setAccessToken * * Arguments * tokens -- array include access token */ public function setAccessToken($tokens) { if (isset($tokens['oauth_token'])) { $this-&amp;gt;access_token = $tokens['oauth_token']; $this-&amp;gt;access_token_secret = $tokens['oauth_token_secret']; } else { $this-&amp;gt;access_token = $tokens['access_token']; $this-&amp;gt;access_token_secret = $tokens['access_token_secret']; } } /** * getAccessToken * * Depend on Library or Functions * Functions * getParameter_AccessToken * parameter2Array */ public function getAccessToken() { $accessTokenURL = SCHEME_HTTPS.HOST_TWITTER.'/'.$this-&amp;gt;api_path['access_token']; $param = self::getParameter_AccessToken(); $response = file_get_contents($accessTokenURL.'?'.$param); return parameter2Array($response); } /** * getParameter_AccessToken * * Return * Parameter for OAuth API(access_token) * * Depend on Library or Functions * Functions * array2Parameter * * Neccesary Variables * consumer_key * oauth_token (request token) */ public function getParameter_AccessToken() { $requestTokenURL = SCHEME_HTTP.HOST_TWITTER.'/'.$this-&amp;gt;api_path['access_token']; $parts = array ( 'oauth_consumer_key' =&amp;gt; $this-&amp;gt;consumer_key, 'oauth_signature_method' =&amp;gt; 'HMAC-SHA1', 'oauth_timestamp' =&amp;gt; time(), // 'oauth_nonce' =&amp;gt; md5(microtime() . mt_rand()), // isn't neccesary // 'oauth_version' =&amp;gt; '1.0a', // isn't neccesary 'oauth_token' =&amp;gt; $this-&amp;gt;request_token, ); return array2Parameter($parts); } /*----------------------------------------------------- request OAuth API -----------------------------------------------------*/ /** * requestOAuthAPI * * Arguments * api_and_format -- api path and format(xml or json) * method -- GET or POST * post_field -- parameters excluding oauth parameters * * Return * OAuth API result * * Depend on Library or Functions * Libs * CURL * * Functions * self::getParameter_OAuthToken */ public function requestOAuthAPI($api_and_format, $method, $post_field) { foreach ($post_field as $k =&amp;gt; $v) { $post_field[$k] = rawurlencode($v); } $httpURL = SCHEME_HTTP.HOST_TWITTER.'/'.$api_and_format; $params = self::getParameter_OAuthToken($api_and_format, $method, $post_field); $ch = curl_init(); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, 1); } curl_setopt($ch, CURLOPT_URL, $httpURL); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); $response = curl_exec($ch); curl_close($ch); return $response; } /** * getParameter_OAuthToken * * Arguments * api_and_format -- api path and format(xml or json) * method -- GET or POST * post_field -- parameters excluding oauth parameters * * Return * Parameter for OAuth API * * Depend on Library or Functions * Critical Std Functions * hash_hmac * base64_encode * * Functions * array2Parameter * * Neccesary Variables * consumer_key * consumer_secret * oauth_token (access token) * oauth_token_secret (access token secret) */ public function getParameter_OAuthToken($api_and_format, $method, $post_field) { $requestTokenURL = SCHEME_HTTP.HOST_TWITTER.'/'.$api_and_format; $parts = array ( 'oauth_consumer_key' =&amp;gt; $this-&amp;gt;consumer_key, 'oauth_signature_method' =&amp;gt; 'HMAC-SHA1', 'oauth_timestamp' =&amp;gt; time(), // 'oauth_nonce' =&amp;gt; md5(microtime() . mt_rand()), // isn't neccesary // 'oauth_version' =&amp;gt; '1.0a', // isn't neccesary 'oauth_token' =&amp;gt; $this-&amp;gt;access_token, ); $parts = array_merge($parts, $post_field); ksort($parts); $params = array2Parameter($parts); $message = $method.'&amp;amp;'. rawurlencode($requestTokenURL).'&amp;amp;'. rawurlencode($params); $key = $this-&amp;gt;consumer_secret.'&amp;amp;'.$this-&amp;gt;access_token_secret; $parts['oauth_signature'] = rawurlencode(base64_encode(hash_hmac('sha1', $message, $key, true))); return array2Parameter($parts); } } /*--------------------------------------------------------- Functions ---------------------------------------------------------*/ /** * hashed array to string * * Argument * array ( * 'key1' =&amp;gt; 'value1', * 'key2' =&amp;gt; 'value2', * ... * ); * * Return * 'key1=value1&amp;amp;key2=value2' */ function array2Parameter($src) { $parameters = ''; if (gettype($src) != 'array') { return ''; } $parts = array(); foreach ($src as $k =&amp;gt; $v) { $parts[] = &amp;quot;{$k}={$v}&amp;quot;; } return implode('&amp;amp;', $parts); } /** * string to hashed array * * Argument * 'key1=value1&amp;amp;key2=value2' * * Return * array ( * 'key1' =&amp;gt; 'value1', * 'key2' =&amp;gt; 'value2', * ... * ); */ function parameter2Array($params) { $items = explode('&amp;amp;', $params); $parts = array(); foreach ($items as $item) { $sp = explode('=', $item); $parts[$sp[0]] = $sp[1]; } return $parts; }訂正(2010-05-04).
tana_ashさんの指摘より、6ヶ所 sigunature となっていた部分を signature に訂正しました。
追記(2010-05-04).最上部に v0.2 のお知らせ
[...] This post was mentioned on Twitter by 清太郎, 清太郎. 清太郎 said: ブログ更新: TwitterでOAuthを使うための学習用ライブラリ Eduwitter http://www13.atpages.jp/~llan/wp/?p=730 [...]
返信削除TwitterでBasic認証の替わりの認証方法について調べる...
返信削除DAC/スパイスラボ神部です。 Twitterでサービスを作る際、Basic認証を使ったものは無数にあるかと思いますが、6月末でそれらがシャットダウンされるにあ......
[...] Twitter API を OAuth で認証するスクリプトを 0 から書いてみた – trial and error PHPでTwitterのOAuthを使うための学習用ライブラリ Eduwitter : Lounge Landscape [...]
返信削除