OpenIDとは
http://openid.net/
サービス運営側の観点でみれば、ある別のサービス(OpenID発行をおこなうところ、認証局とでも呼ぼう)がユーザの認証を行うことで、自分のサイトの認証に替えること。
運営者は認証というとてもfragileでdullなことをやらないで良くなり、サービス開発に専念出来る、といいなぁ。
日本で一般的みたいなOpenID認証局
http://openid.net/get-an-openid
http://yusukebe.com/archives/08/04/15/170746.html
少し前の情報だが、Yahoo Japanの利用率がすごいらしい。現在ではこれにmixiなどが加わっていることだろう。日本でサービスを展開するなら、信用度も加味するとmixi,Yahoo,はてな くらいにのみ対応しておけば良さそうだ。
Googleもサービスを行うらしいが、bloggerでサイト開設する必要があるようなので、あまり一般的ではなさそう。
今はもっと簡単になって、http://www.google.com/profiles/ユーザ名 でいいようだ
テストするなら、やはり全アカウントで試さなければいけないわけだが、こういうサイトに登録するのは最も嫌いなので、友人に頼もうかな。
実装する
もちろん、PHPでウェブベースで実装する。
- PHP OpenID Libraryというものを使う。現在日本のサービスの多くはOpenID2.x系を発行しているようなので、こちらを使う。
- 環境のチェック
- どこかの環境にアップロードorコマンドラインからexamples/detect.phpを動かす。システム要件が整っているかを調べてくれる。
- 私の場合はSetup Incompleteとなった。(ちなみにサーバは自宅サーバ。VineLinux5+PHP5.2)
- Math Support(多倍長計算用)が必要らしい。GMPをインストールしよう。
- curlがあった方がいいらしい
- SQL系の何かを使う場合は、PEAR DBが必要。
ということで、PHPのバージョンアップと同時に組み直す。
- Auth/のディレクトリをPHPのインクルードパスに移動する
- examplesの中身を動かしてみる
- READMEによると、php.iniのopen_basedirを次のように変えなければいけない
open_basedir = "/tmp:..."
- 次にexamples/consumer内のファイルをブラウザから実行できる場所に移し、ブラウザベースで実行する。
- serverは関係なさそうなので省略。
- READMEによると、php.iniのopen_basedirを次のように変えなければいけない
と、ここまでやろうとしたところでサーバが壊れたので断念。
- やっぱりZendFrameworkでやる。
http://framework.zend.com/download/latestから最新版をダウンロードし、解凍 - 中身のlibraryをすべてphpのライブラリ読み込みが行われるところにおく(すなわち、/usr/share/php5-pear)
- http://devsrv05.com/_reference/ZendFramework/zend.openid.consumer.htmlのサンプルをロードして動作テスト。うまくいきました。
しかし、ZendFrameworkでは2.0が扱えないことが判明。すなわちYahooやmixiのユーザには使ってもらえない。これはダメだ。ということで放置。
PHP OpenID Libraryのソースを読んでわかったこと
Discoverの処理の流れ
- 対象となる文字列(URIですらないかもしれない)が与えられる
- この文字列は次のような形がありうる
- http://example.com/ (URI)
- https://example.com/ (URI)
- example.com (URI)
- xri://=example (XRI)
- =example (URI)
- @example (URI)
- XRIについては、ITMediaの記事で概要がわかる。
- 次にAuth_OpenID_discover 関数に行く。
- SSLが使えるかチェック。URIが明示的にhttpsで与えられていて、使えなかったら、帰る。
- XRIかURIか判定
- それぞれAuth_OpenID_discover***に行って、OpenIDを出すURIのリストを受け取る。
- URIは、URIのノーマライズ*1を受けた後、Auth_OpenID_urinorm()で形を整える。最後に#以下のアンカーを外す。
- ノーマライズされたらAuth_OpenID_discoverWithYadis()に行く。だめだったらAuth_OpenID_discoverWithoutYadisにいく。
Auth_OpenID_disvoberWithYadis()の処理の流れ
Discover OpenID services for a URI. Tries Yadis and falls back
on old-style <link rel='...'> discovery if Yadis fails.
Might raise a yadis.discover.DiscoveryFailure if no document
came back for that URI at all. I don't think falling back to
OpenID 1.0 discovery on the same URL will help, so don't bother
to catch it.
とある。
- 特に指定しない限り、discover_functionはAuth_Yadis_Yadis::discover($url,&$fetcher)
- このなかで、$fetcher->get()を呼び、HTTPヘッダをセットして対象の内容を取得する
- $fetcher->dataと$fetcher->headersにデータが入る。
- うまく取得できたらログを書いて*2、Auth_Yadis_HTTPResponseオブジェクトを返す。
- Auth_Yadis_HTTPResponseは連想配列みたいなもの。
- final_url:最後に取得したURI
- status:レスポンスコード。200とか500とか
- headers:レスポンスのヘッダ
- body:返された中身。XMLdocやHTMLdoc
- これをAuth_Yadis_DiscoveryResultに展開しながら、次の二点をチェック
- XRDSコンテンツか
そうだったら終わり。 - XRDSの位置を示したheaderを持っているか
もっていたら、そのアドレスを再びgetしてみる。
とれれば、正常終了 - 持っていない場合、HTML解析*3をして、再び試す。
- XRDSコンテンツか
- このなかで、$fetcher->get()を呼び、HTTPヘッダをセットして対象の内容を取得する
- 失敗していないかチェックしたあとで、Auth_OpenID_ServiceEndpoint::fromXRDSに渡す。
- ここでXRDSからopenid_servicesがとれればめでたくおしまい。endpoint_filterの関数を呼んで、結果を返す。
- 私の場合はここで$yadis_servicesが空になっちゃってるみたい。
そこで、フィルタの設定をなしにしたら、うまく取れました。 でもこんなことでいいのか?
- 取れないと
- きちんとXRDSが返されているときは、Auth_OpenID_discoverWithoutYadisを呼ぶ。
- Auth_OpenID_discoverWithoutYadisではuriから再びレスポンスを形成し、HTML解析でEndPointsをとりにいく。
- 返されていないときは続行。その後HTMLからEndpointsを取りに行く
- きちんとXRDSが返されているときは、Auth_OpenID_discoverWithoutYadisを呼ぶ。
http://cream.s35.xrea.com/contents/tech/28.htmlの研究
親切にも、サンプルをネットで公開した上でソースを公開してくださっている。比較デバッグが可能という点で、最高である。
トラブル1:MySQL周り
これは完全に私側の問題。
apt-get install mysql-sever apt-get install mysql-client apt-get install php5-mysql pear install db
などとして、php.iniでmysqlのエクステを有効にしたら問題なく通る。
トラブル2:結果が表示されない
切り分けた結果、問題は
$response = $consumer->complete($return_to);
において、$responseがNULLであること。これをそのまま通すソースを書いてるライブラリとは何とひどいんだろう。内部で次のようなエラーが出てる。
Notice: Trying to get property of non-object in /usr/share/php5-pear/Auth/OpenID/Consumer.php on line 434 You cant get response.
ひとまず、返却先を?action=finishedにして、これを手がかりに分岐させる。当然だが変化なし。
直接$reponseをセットしているのは/Auth/OpenID/Consumer.phpのl.430
$response = $this->consumer->complete($message, $endpoint, $current_url);
なので、これを直接解析すれば良さそう。
解析した結果、まずわかったのは、「はてなでは、PHP OpenID Libraryが予想した形で引数を返していない」ということである。Library側では、openid.modeのようになってるはずなのに、hatenaはopenid_modeと返している。
これはAuth/OpenID/Message.phpを書き換えることで対応できる。GETのスクリプトをそのまま書き出してみたら、ちゃんと"."になってる。おそらくライブラリ設計者が間違えたんだろう。PHPでは.は_に変換されるそうだ*4
よくよく自分のスクリプトを見直してみたら、completeに引数でGETを与えてた。標準では全く別の経路から取ってくるみたいで、こっちだとちゃんと.でパースされる。
--$parts = explode('.', $key, 2); ++$parts = explode('_', $key, 2);
さらに続けた結果、Auth_OpenID_GenericConsumer::completeの第二引数が参照渡しになってないために、id_resがうまく呼べてないことも原因だった。呼んでいる関数自体を参照渡しにすることで、継承的に参照渡しできるようにして解決。
また、同じくAuth_OpenID_GenericConsumer::_discoverAndVerifyも。ここはuserfunc呼びなので、引数の直前に&を挟んで解決。しかしこれはPHP5.3ではいかんらしい。どうすればいいのだ。
参照渡しになっていないというミスは、関数呼び出しを停止してるみたいなので、無視しちゃまずいようです。
今回の教訓は、noticeはまだしも、warningは無視出来ない。無視するのを進めてくるPHP OpenID Library作者はいけませんね。しかし今現在でもこのライブラリが主流なの?ってぐらい更新もバグフィックスもなされてないけど。。。
トラブル3:XRDS使用のページが取れない
上のははてなで実験し、はてなはXRDS不使用のため(URL直というか)とれたが、yahoo,livedoorなどで失敗することがわかった。
Warning: Parameter 1 to filter_MatchesAnyOpenIDType() expected to be a reference, value given in /usr/share/php5-pear/Auth/Yadis/XRDS.php on line ***
明らかにまたこいつのせい。--if (call_user_func_array($filter, array($service))) { ++if (call_user_func_array($filter, array(&color(Red){&};$service))) {わかってしまえば簡単だね。
トラブル4:いくつかのサーバ相手にnonceが切れる
ex.Google,Livedoor,YahooJp
原因:自分の鯖の時計があってない。
VMware上で開発してるために、サスペンド中に時計がずれる、ということを忘れていた。
時間の同期をしたいが、なぜかvmtoolsのCDを読み込んでくれない。VineLinux5+VMwareFusion3です。ご存じの方は教えてください。
ntpdate ntp.nict.jp
などとやって、終了。
対応状況のようなもの。
- 標準でついてる、discover.php(on 自宅開発環境)
- フルパス系はgoogle以外成功。すべてYadisで失敗、Yadis不使用で成功
- Yahoo
- hatena
- livedoor
- フルパス以外は全部失敗
- Yahoo(https):Yadisを使って失敗
- Yahoo(http):Yadisを使っても使わなくても失敗
- livedoor:使っても使わなくても失敗
- サマリ:Yadisが動いてない。
- 解決策:fromXDLSのところのフィルタ使用をなしにする。結果にも問題なく取得出来る。
- フルパス系はgoogle以外成功。すべてYadisで失敗、Yadis不使用で成功
- http://mixi-openid.appspot.com/
- Yahoo! Japan:成功(ドメイン,フルパス)
- はてな:成功(フルパス)
- google:成功(フルパス)
- livedoor:成功(ドメイン)