OpenID

Last-modified: 2010-05-08 (土) 15:37:45

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でウェブベースで実装する。

  1. PHP OpenID Libraryというものを使う。現在日本のサービスの多くはOpenID2.x系を発行しているようなので、こちらを使う。
  2. 環境のチェック
    • どこかの環境にアップロードorコマンドラインからexamples/detect.phpを動かす。システム要件が整っているかを調べてくれる。
    • 私の場合はSetup Incompleteとなった。(ちなみにサーバは自宅サーバ。VineLinux5+PHP5.2)
    • Math Support(多倍長計算用)が必要らしい。GMPをインストールしよう。
    • curlがあった方がいいらしい
    • SQL系の何かを使う場合は、PEAR DBが必要。
      ということで、PHPのバージョンアップと同時に組み直す。
  3. Auth/のディレクトリをPHPのインクルードパスに移動する
  4. examplesの中身を動かしてみる
    • READMEによると、php.iniのopen_basedirを次のように変えなければいけない
      open_basedir = "/tmp:..."
    • 次にexamples/consumer内のファイルをブラウザから実行できる場所に移し、ブラウザベースで実行する。
    • serverは関係なさそうなので省略。

と、ここまでやろうとしたところでサーバが壊れたので断念。

  1. やっぱりZendFrameworkでやる。
    http://framework.zend.com/download/latestから最新版をダウンロードし、解凍
  2. 中身のlibraryをすべてphpのライブラリ読み込みが行われるところにおく(すなわち、/usr/share/php5-pear)
  3. http://devsrv05.com/_reference/ZendFramework/zend.openid.consumer.htmlのサンプルをロードして動作テスト。うまくいきました。

しかし、ZendFrameworkでは2.0が扱えないことが判明。すなわちYahooやmixiのユーザには使ってもらえない。これはダメだ。ということで放置。

PHP OpenID Libraryのソースを読んでわかったこと

Discoverの処理の流れ

  • 対象となる文字列(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をして、再び試す。
  • 失敗していないかチェックしたあとで、Auth_OpenID_ServiceEndpoint::fromXRDSに渡す。
    • ここでXRDSからopenid_servicesがとれればめでたくおしまい。endpoint_filterの関数を呼んで、結果を返す。
    • 私の場合はここで$yadis_servicesが空になっちゃってるみたい。
      そこで、フィルタの設定をなしにしたら、うまく取れました。 でもこんなことでいいのか?
  • 取れないと
    • きちんとXRDSが返されているときは、Auth_OpenID_discoverWithoutYadisを呼ぶ。
      • Auth_OpenID_discoverWithoutYadisではuriから再びレスポンスを形成し、HTML解析でEndPointsをとりにいく。
    • 返されていないときは続行。その後HTMLからEndpointsを取りに行く

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のところのフィルタ使用をなしにする。結果にも問題なく取得出来る。
  • http://mixi-openid.appspot.com/
    • Yahoo! Japan:成功(ドメイン,フルパス)
    • はてな:成功(フルパス)
    • google:成功(フルパス)
    • livedoor:成功(ドメイン)

*1 Auth_OpenID::normalizeUrl、http or httpsがついてるか判定し、ついてなかったらhttpをつける
*2 Successfully fetched '%s': GET response code %s
*3 metaタグをピックアップして、XRDSの場所が書いてあるタグを探す
*4 http://www.php.net/manual/ja/language.variables.external.php