OpenSocialのサービスプロバイダっぽいのを作ってみる 6.OAuth Core 1.0a(3legged OAuth)を実装してみた

実装してみた。
仕様は前回の記事参照。
作っているうちにどんどん混乱してくるから、先にまとめておいてよかった。
今回の内容は、オフィシャルドキュメントの内容ではなく、個人的にこうやった、というまとめ。
 
今週の月曜日、mixiアプリが正式コンテンツになったっぽい。
mixiがやっとベータじゃなくなったらしい。
 
今週の金曜日、Macの新しいOS「Mac OS X Snow Leopard」が発売したらしい。
Leopardユーザは3,300円でアップグレードできるらしい。
見た目はそんなに変わらないらしいけど、90%の改良を加えたとか書いてあった。
 
金曜日の夜から土曜日の朝(もしくは昼)にかけて、完全趣味でやるプログラムが気持ちよくて仕方がない。
スーパー趣味プログラムタイム!!
 

データベースにトークン保存用のテーブルを作成

とりあえずこんな感じで。
oauth_consumer_keyも内部で保存してあるID(serial)に置き換えようかと思ったけど、めんどくさくてそのままにした。

create table oauth_token(
	oauth_consumer_key varchar(255) not null,
	oauth_callback varchar(255) not null,
	oauth_token varchar(255) primary key,
	oauth_token_secret varchar(255),
	oauth_verifier varchar(255),
	oauth_timestamp bigint not null,
	type enum('request','authorize','access') not null
);

 

ランダムなトークンを生成する関数

こんな感じで。
これなら[a-zA-Z0-9]+で、文字数も非固定だから推測されにくい!

<?php
function getRandomToken($c=2){
	$token = '';
	for($i=0;$i<$c;$i++){
		$m = mt_rand()%2 ? 'sha1' : 'md5';
		$token .= $m($token.microtime().mt_rand(),1);
	}
	$token = base64_encode($token);
	$token = str_replace(array('=','/','+'),array('','',''),$token);
	return $token;
}

 
$c=2の時

QdUCr0qMfP6jEgmU1xkZpiTFIswnZA9b0Q83sD6sfnHvWyzZOcq3Q
d44W9wLNCs2CPr1pmviegyijV8bXfudPlYcMYnWPugsfPpZ4Vw3lA
SLmAH6fWOW3ztkBuMNZWLIvG1fUamZ4VpRxxpeipplPHm
58QmD93S0btqtIFd91LRbK5yF9TmCF2l0ZYBnmAZvjks
MmAH10mfNyRBWJsx1gUhH93O3O5GSMJPVDDY81uZKTDuG19Q
YD7yHoARaHGiPPrCisfN2jG3uO2MK0HXVtqKEMZp28ijzqF
kIQDjnr8xWE7B98jqa0q1QqM5b5Zct49rd0X5H2CnGXht
Q7bGfycQXJbsB3GT4JTihRQXLLi6efxPIrhTlNl7MmiCEOLW0CCatA
YUE95nMwffv6nD1nxR7tza5vRVVqYGrMjl4CGVhjKzbFsJ5bang
B6io5TqKp0eV5Vj6GKcWdzdcAVaHwZAgmi07hcTHkt4bvRacnvRjQ
IITMa0SPDMgnJfzQfYthVisgaI87lLOmpHONzgtEu8cE2Z8NwDMl4A
jVKzbyH23VhOdZ0g5kdreCjUQjVjiSVVKqinhF197q9UANl5
dk9TwQlUZpNN6jo8E5E0rSdBR00GzLVnaRv21aDO63PjAQB8
8M5hN9ImDsHqf0zr6CYKB0k0XWgKmvizPsZsQf90VdUpFK
6xQehSqT0gO9ZZWKVo4zdCsghZzy9j5HqzSW8qBGLN9lEKI
0ZpAmzUZxrjW0kuZe4fBIwMIWUGQecCLMzeqKhsH1VK1DrQsLlZ1mw
GgZHn7ApuZDhh3uB7OuJQS5wd1SIALu6TReiLcwXplJJRFNGBSng
AdMOc5R1tZyjBFOdObVKj4ItO95h70Qe3VckmQdqrti5mo
itI9hNWqhabKpO8MAOg259Chh2VIdy4wKzl5kEW3EP
S6kiDLNB3YWV8WGOKXonKbM9S5QrbRcZROJYAYBXtRB

 
$c=3の時。

M3dh28Bxw2A6oPBaScEEDvHlHDe8dmwWLqs700yBQMFvaEshXc1RUDW2G97DQqHY9IXHyx7GN9p8N6
h3GooIF4vdivG7Kwgx6iibfWpEBmyhzocOWfqmUqCehfpkbjCtOYOq5ATs7JciYjsAyA
I8h0Thb4U02iK70xqNMc0tonFE1FVOJplPa6JhGNQBkvis74CHNLkFFoQyOJ2YrLA
5Id8kakGw6CW1fFmkSAZRzEME2rFyJATReZtSH8yKyTzBJ6J6ShnGASTZRMCqgQo8kTdIQ
NOeDNbByChrfUVeU3BhLUk1SMhgaTRYfqm57eoCJG1dpjSSLPr6b03PKfwoabnr5t39geG81RU
oThax6C2UrDihPkWRmbkBRxCBfTAX0zpjsp35Pjv4je3BlJdVbAKNKNluR85aVUq72jJAo
MtWppVWoA51Y70bJvC2CZNetXkfLXaiRupR82gC1k4ZB0GhMAEcQuEgHlndh2cCzQzMv1qcME
5nQ24nXiz86F0EK7Czl4aUP1kMmPlwq6cOO57FZ6iofUP9RZbXdaaOOpvmHJKoB7EFQ
TTIW5PzqqeuFcJ27D9YTXIJ68aeDMGkvwLNLvPq39QXKMNfwNgo9eus6s9oEAqHMGA0ky0cL3M
5UhjyU2tKTrltu0tYYqj9ZGjZ606ucQ6fp0VnrfETrhm8BEZ3ggsSGekJOAPpDDgy3A
5KeWIp538SLiTAbgXR1E9SY3NltmzURk0kVc6t3TX4HSNmy2coCfJjDSmaekw8zbln2IA
7xAYJ1MQ5hdzaVxbPNXK11qiQgMCF20Lejh5Thhy12FlSs6zJvgVbZAuHCjCWRpaHfD0ArVeiWLz1C
K9trl9hHsXSBkpCygwScPUgvQvFXqYhg9ZB5EbmBq6okAqBHd8gK0NxidcUhFwvoi69w
eKxLzwLOYFjG2VpBHjqCFQ1gTF62HyiulhVLRvDSYrpEn7hXwMKnXxkdJJ331UFA
DMlagKmTb4yX5uRnYpnqLIaRUxuj4uekahTyWuw1gnMykHrwQS6W1kwAWDGsBm7AeCT69dBpFw
ArEtbX6BvqT05appebcDUsjOsuRsz0L4earLgDZgFZkBHWgsDeiuWHGmQWHTIWBxF99nGm19OM
4s19C7cYitfb1Afv096KmkkeOMBFAqES8iyz05ACDjOeZUiRykXxuiqZmzHLL9vKd4bKkQtc
I1PMf2K1EsvqCxnyx6Mi7ItO6nDoufXRNtNEV6fvIpAWqz6KguoBA635TtuBV
QjBRRkG3FpCPDVmKpna6FK0yDXLDWoXROzsaMTcuM7oQ679OdwK74irralpWd992D9MbjT7CVV7H
qJ4js2XNTI3rzu6A95vzsqsWmCIf52Be6ybcdwn0d6etOFLznBROSGhVU9wpQ2GElUFEGgnQgz6frqP

 

リクエストークン発行

  • OAuth認証
  • 有効期限の過ぎたトークン削除
  • パラメータチェック
    • oauth_callback
  • データベース登録
    • oauth_consumer_key
      • コンシューマキー
    • oauth_callback
      • コンシューマが送ってきたパラメータ
    • oauth_token
      • ランダム生成
    • oauth_token_secret
      • ランダム生成
    • oauth_timestamp
      • コンシューマが送ってきたパラメータ
    • type
      • 'request'
  • レスポンス
    • oauth_token
      • さっきランダム生成したoauth_token
    • oauth_token_secret
      • さっきランダム生成したoauth_token_secret
    • oauth_callback_confirmed
      • 'true'

 
コンシューマが受け取るパラメータはこんな感じ。
レスポンスボディをparse_strした結果。

Array
(
    [oauth_token] => 0qu11xIjGx64NUHrsJ9jV1FvuGvBMDzTYPQzvoxTwUjL
    [oauth_token_secret] => 8U2YjORAoOk3hhXwbWYaiBUEwKyQf2lRhAE8wYEQxhzQzjsXccE0TrZi88t5xGbb8hug
    [oauth_callback_confirmed] => true
)

 

ユーザ認証 1

認証フォームを表示。

  • 有効期限の過ぎたトークン削除
  • パラメータチェック
    • oauth_token
  • 送られてきたoauth_tokenが有効かチェック
  • ユーザがログインしているかチェック
  • ログインしていない場合はログインフォーム表示
  • 認証チェックフォーム表示

 

ユーザ認証 2

ユーザは、同じURLに認証可否結果パラメータ付きでアクセスしてくる。

  • 有効期限の過ぎたトークン削除
  • パラメータチェック
    • oauth_token
  • 送られてきたoauth_tokenが有効かチェック
  • ユーザがログインしているかチェック
  • ログインしていない場合はログインフォーム表示
  • ユーザが許可したかチェック
  • データベース更新
    • set
      • oauth_token
        • ランダム生成
      • oauth_verifier
        • ランダム生成
      • type
        • 'authorize'
    • where
      • oauth_token
        • 送られてきたoauth_token
      • type
        • 'request'
  • レスポンス
    • oauth_token
      • さっきランダム生成したoauth_token
    • oauth_verifier
      • さっきランダム生成したoauth_verifier

 
コンシューマが受け取るパラメータはこんな感じ。
$_GET。

Array
(
    [oauth_token] => 6IxekxUqBobCWQZgTYq7WYDveQWzg2uRSz8mnGhphY1UAWz
    [oauth_verifier] => B3NsPOxusGrz4nxRpb91zJYD33YzKLWZ53sy4moekno2rSvOpZFkOpCgio7fmuG7pQw
)

 

アクセストークン発行

  • OAuth認証
  • 有効期限の過ぎたトークン削除
  • パラメータチェック
    • oauth_token
    • oauth_verifier
  • データベース更新
    • set
      • oauth_token
        • ランダム生成
      • oauth_token_secret
        • ランダム生成
      • type
    • where
      • oauth_consumer_key
      • コンシューマキー
      • oauth_token
        • 送られてきたoauth_token
      • oauth_verifier
        • 送られてきたoauth_verifier
      • type
        • 'authorize'
  • レスポンス
    • oauth_token
      • さっきランダム生成したoauth_token
    • oauth_token_secret
      • さっきランダム生成したoauth_token_secret

 
コンシューマが受け取るパラメータはこんな感じ。
レスポンスボディをparse_strした結果。

Array
(
    [oauth_token] => DnWFL4DZhGrk82hDPiplfaq0XLtvyllXfuUv9G4DSmQtW1
    [oauth_token_secret] => 2m26aQDyGNvufC5i4p4hEod1B9McmcnMEGSU4KkG4tKJmkO4krgU3hrUjjXS7Ag
)

 

保護されたリソースを返す

コンシューマはこの後、RESTful APIを叩く。

  • さっき受け取ったoauth_tokenとoauth_token_secretでOAuth認証
  • リソースを返す

 
以上!
アクセストークンは、とりあえず300秒有効って事にしてる。
 

サブドメインでセッションIDを共有してしまっていた

サブドメインドメインを分けて、それぞれコンシューマ役とサービスプロバイダ役に見立ててやってみた。
どうやらセッションIDを共有しているらしく、別の環境とは言いがたい。
で、セッションIDを分けてみた。
問題については、↓ここが解りやすい。
 
セッション値漏洩 : アシアルブログ
http://blog.asial.co.jp/173
 

VirtualHostごとにセッションIDを分ける方法

httpd.confのの中に1つずつこう書けばいいらしい。

php_value session.cookie_domain xxx.jp

 
…と思ったら、違う、IEの仕様(バグ)?
結果からいうと、1行追記する事で返ってくるヘッダーは変わるものの、どちらにしろ挙動は変わらなかった。
FirefoxOperaだと、サブドメインでは分けてくれる。
IE(Sleipnir)だと、sub.xxx.jpに行ってからxxx.jpにすると分けてくれて、xxx.jpに行ってからsub.xxx.jpに行くと共有してしまう。
IEは、デフォルトでCookieはxxx.jpとそのサブドメインまで有効、という認識らしい。
 

short_open_tag

前にPHPのバージョンを5.3.0にアップデートした時に、↓こう書けなくなったって書いたけど、どうやらphp.iniのshort_open_tagで変えられるらしい。
未確認。

<?php
$user = "ゲスト";
?>
こんにちは!<?=$user;?>さん