Your browser (Internet Explorer 6) is out of date. It has known security flaws and may not display all features of this and other websites. Learn how to update your browser.
X
Post

PHP OpenId Libraryを使ってstatelessモードで認証する方法

一週間ほどdocomoのOpenIdで派手に詰まって、
それの対応がようやくできたのでその対応方法について。


これまでの記事:
docomoのOpenIdで詰まった
続・docomoのOpenIdで詰まった
続続・docomoのOpenIdで詰まった
docomoのOpenIdで詰まった・終

と言っても、こんなのをドヤ顔で発表されても
回りが反応に困るくらいの原始的な対応なんですが。


今回は自分はdocomoのOpenIdのための実装だったため、
こちらにあるPDFを元に説明します。
PDF中の64ページ目あたり、doAuthRequest()関数で処理が行われます。


function doAuthRequest() {
    error_log("[sample-rp] doAuthRequest start.");

    $docomo_op_identifier = "https://i.mydocomo.com";
    $consumer = getConsumer();

    // SHA256のみでネゴシエーションする
    $order[] = array('HMAC-SHA256', 'DH-SHA256');
    $consumer->consumer->negotiator =& new Auth_OpenID_SessionNegotiator($order);
    // 認証要求を開始する
    // ディスカバリとアソシエーションの確立を行う
    $auth_request = $consumer->begin($docomo_op_identifier);
    if(!$auth_request) {
        errorExit("認証要求が失敗.");
    }
    
    // OpenID 1(リダイレクト送信)か確認する
    if ($auth_request->shouldSendRedirect()) {
    // OpenID 1 の場合は未サポート
        errorExit("認証要求がOpenID 1なので未サポート.");
    } else {
        // 自動POST画面を生成する
        $realm = getRealm();
        $returnTo = getReturnTo();
        $form_id = 'openidparam';
        $form_html = $auth_request->htmlMarkup($realm, $returnTo,
                                               false, array('id' => $form_id));

        // 自動POST画面の生成が正常か確認する
        if (Auth_OpenID::isFailure($form_html)) {
            errorExit("画面自動生成エラー: " . $form_html->message);
        } else {
            // 自動POST画面を出力
            print $form_html;
        }
    }
    error_log("[sample-rp] doAuthRequest end.");
}

この中の$form_htmlが認証のためのPOST送信用コードです。
ここで出力されるコードは次の通りです。


<html><head><title>OpenId transaction in progress</title></head><body onload='document.forms[0].submit();'><form accept-charset="UTF-8" enctype="application/x-www-form-urlencoded" id="openidparam" action="https://XXXXXXXXXXXXXXXX" method="post">
<input type="hidden" name="openid.ns" value="http://XXXXXXXXXXXXXXXX" />
<input type="hidden" name="openid.realm" value="http://XXXXXXXXXXXXXXXX" />
<input type="hidden" name="openid.mode" value="XXXXXXXXXXXXXXXX" />
<input type="hidden" name="openid.return_to" value="XXXXXXXXXXXXXXXX" />
<input type="hidden" name="openid.identity" value="XXXXXXXXXXXXXXXX" />
<input type="hidden" name="openid.claimed_id" value="XXXXXXXXXXXXXXXX" />
<input type="hidden" name="openid.assoc_handle" value="XXXXXXXXXXXXXXXX" />
<input type="submit" value="Continue" />
</form>
<script>var elements = document.forms[0].elements;for (var i = 0; i < elements.length; i++) { elements[i].style.display = "none";}</script></body></html>

これが画面に出力されると同時に認証のためのPOSTを送信するようになっていますが、
この中でopenid.assoc_handleがAssociationのハンドルになります。
POST送信する際このパラメータがなければStatelessモードで送信します。

そのため、該当部分のinputタグだけを出力しないようにできればそれで解決なのですが……
ライブラリの中を軽く調べてもオプション指定などで
アソシエーション要求しない処理に切り換える事はできなさそうです。
個人的に人のライブラリに手を加えるのは極力避けたいため、
ライブラリはいじらずにHTMLの生成はさせて
出力直前に該当タグだけを除去する事により対応しました。


        // 自動POST画面の生成が正常か確認する
        if (Auth_OpenID::isFailure($form_html)) {
            errorExit("画面自動生成エラー: " . $form_html->message);
        } else {
            // openid.assoc_handleのタグを削除
            $pattern = '/<input("[^"]*"|\'[^\']*\'|[^\'">])*name=\"openid\.assoc_handle\"("[^"]*"|\'[^\']*\'|[^\'">])*>/';               
            $form_html = preg_replace($pattern, '', $form_html);
            // 自動POST画面を出力
            print $form_html;
        }


本当に我ながらひどい対応方法だ (´・ω・`)
アソシエーション確立してるのにそれを使わないStatelessで認証というのが
自分でも違和感ありすぎる。
まあ、何故かアソシエーション確立してもそれで認証通らないから仕方ないんですが……
ライブラリの中身をじっくり見ながら修正するだけの気力あればそれで行ったんですが、
一週間ほど行き詰まってスケジュール押してたからこれで勘弁してくれとなってしまいました。


他にこのライブラリを使ってStatelessモードで
認証する方法をご存知の方いたら一報いただけると幸いです。

TrackBack URL :

  • こんにちわ!
    こちらの記事を参考にdocomoOpenIDの実装に苦しんでおります。
    OPがxrdsを取りに来たにも関わらず、未だ認証成功しておらず、苦戦中です(汗

    ところでこちらのライブラリでStatelessで使用する方法ですが、記事が書かれてから半年以上経ってますが、今更のようにコメントします。
    ソースコードを追いましたところ、”Auth_OpenID_GenericConsumer”のコンストラクタに”null”を渡すと、Statelessで動作するようです。
    つまり、getConsumer関数中の

    $consumer =& new Auth_OpenID_Consumer($store);

    の部分を

    $consumer =& new Auth_OpenID_Consumer(null);

    とすれば、Statelessで動作します。
    実際に生成されるフォームから”openid.assoc_handle”が消えることを確認しました。
    Statelessモードの場合はstore_pathのディレクトリを作る必要もないようです!

    しかし・・・こちらの記事が無ければさらに長々と詰まるとこでした・・・ありがとうございます~

    好鈴

    2012年12月19日

  • 好鈴さん>

    どうも、コメントありがとうございます。

    > OPがxrdsを取りに来たにも関わらず、未だ認証成功しておらず、苦戦中です(汗

    openid認証を最初に実装する時は色々分からない事多くて手探りですよね。
    特にきつかったのはstatelessモードについて調べるところでしたが、
    私も他にも結構詰まった箇所がありました。
    大変だとは思いますが頑張って下さい。

    > ソースコードを追いましたところ、”Auth_OpenID_GenericConsumer”のコンストラクタに”null”を渡すと、Statelessで動作するようです。

    なるほど、コンストラクタで指定でしたか……この部分は考えていませんでした。
    これでうまくいくか試してみます。ありがとうございます。

    straysheep

    2012年12月20日

  • どうも、ダメでした・・・
    Auth_OpenID_GenericConsumerのコンストラクタにnullを渡すと、$this->storeがnullになるのですが、コンストラクタで

    $this->_use_assocs = (is_null($this->store) ? false : true);

    として、storeがnullかどうかで、_use_assocsを定めています。
    _use_assocsがfalseなら、_getAssociationはnullを返します。
    _getAssociationがnullを返すと、beginメソッドでAuth_OpenID_AuthRequestを作るときに$this->assocがnullになります。
    その結果、フォームから”openid.assoc_handle”が消えるのですが(長い!)

    Auth_OpenID_GenericConsumer::completeメソッドで「_complete_id_res」が呼ばれたときに、なんと_use_assocsがfalseかどうかを確認していないのです!
    結果、$this->store->getAssociationメソッドを呼ぼうとするも、$this->storeはnullなのでエラーになります。
    どう考えても実装ミスです。これは、ライブラリを修正せずに、Statelessモードは使えなさそうです・・・

    好鈴

    2012年12月20日

  • 動作確認しました……本当だ、これは駄目だ。

    そうするとライブラリ直すか、
    認証直前にassoc_handleを消すしかなさそうですね。
    結構重大な欠陥だと思うんですが、statelessモードって他じゃそんなに使う機会のないモードなんでしょうか。


    ところでこの記事書いた後に他の携帯キャリアのopenid実装をやる機会がありましたが、
    サンプルプログラムではほとんどこのライブラリを使って記述されていますね。

    結構クラス構造が複雑で処理を追跡するのに苦労するんですが……

    straysheep

    2012年12月20日

  • 横やり、失礼いたします。
    現PJにて、statelessモードを実装せずにて、docomoログイン画面を
    表示することに成功しまた。
    このブログにはお世話になりましたので、ご報告いたします。

    まず、statelessモードを採用することは、docomo側では
    認可されています。(ガイドラインにあるとおり)
    ですが、statelessモードとは、署名認証を自己で行うように
    推奨されおり、OPENID2.0準拠としては、セキュリティ向上意識のもと
    行う必要があると思われます。
    それはとりあえず、おいておき、

    今回の話題となっている、Associationの問題ですが、
    ガイドラインにある、Openid認証時に実は、自動POST画面で
    送信を行います。(一見、認証要求で行うようなシーケンスにみえますが)
    ここで、Association確立時に渡される応答秘密鍵を再度
    自動POSTで送信し、OP側とRP側での鍵マッチングを行うようです。
    なので、アンマッチになることでエラー画面が表示されるのです。
    アンマッチの原因は、OpenIDのAuthライブラリにある、
    Message.phpの内部で、687行目の「name, urldecode($value));」が
    原因です。
    デコードを行うことで、tmp(associations)に格納されているファイルとの
    オリジナルと変わっていることが分かります。
    具体例では、オリジナルが”+”となっているところが、デコード後は
    半角SPとなっているのが分かります。
    これが、アンマッチとなる原因になります。

    で、やっつけ方ですが、2通り考え方があります。
    ①ライブラリに手入れる。
    Message.phpのgetMessage関数を参照している、
    assoc_handleを形成しているとこを、urlencodeする。
    ②login.phpにて、独自に解析しエンコードを行う。

    ②がセオリーかなと思います。①だとライセンスや、yumの
    updateなどの問題もありますし。
    私としては②で向かいますが、まだ具体的な解決方法までは
    至っておりません。

    とりあえず、ご報告まで。
    ご参考になれば幸いです。

    ten

    2012年12月21日

  • urldecodeはrevertされてmasterにmergeされてますね。
    https://github.com/openid/php-openid/pull/83

    匿名

    2012年12月28日

  • 匿名さん>

    コメントありがとうございます。
    最新のソースファイル確認しましたが確かに直っていました。

    これなら最新版でこの問題は解決しそうですね。

    straysheep

    2012年12月30日

  • その後、ソースを追って、assoc_handleがurldecodeされてformが生成されているために、docomo側でアソシエーションハンドルが見つけられずエラーとなっていることを突き止めました!

    ・・・と、書こうと思ったら先に書かれてましたっ!!
    で、確かに、docomo側から返却されるassoc_handleに”+”が含まれない場合のみ、ソースを修正しなくても正常に動作するようです。
    はた迷惑なurldecodeでしたね・・・

    好鈴

    2013年1月10日

  • 好鈴さん>

    やはり同じところに行き着きましたか。

    最新バージョンのライブラリだとurldecodeが外されてるので
    この処理が不要だったという事で間違いないみたいですね。

    straysheep

    2013年1月12日

  • tenさん>

    初めまして、コメントありがとうございます。

    該当の鍵の部分の処理を確認しました。
    なるほど、ここで整合性が取れなくなっていたのですね。

    これをヒントに解析をしてみたところ、
    ライブラリの外部からはメンバ変数

    $auth_request->assoc->handle

    でassociationの鍵にアクセスできるようです。

    urldecodeされる前にurlencodeすれば問題は解決するので、
    doAuthRequest()関数中でPOST用のhtmlを生成している行、

    $form_html = $auth_request->htmlMarkup($realm, $returnTo,
    false, array(‘id’ => $form_id));

    これの直前に

    $auth_request->assoc->handle = urlencode($auth_request->assoc->handle);

    として、urlencodeをかけて上書きするとズレはなくなりました。
    これでStatelessにせずとも正常に動作するようになりました。

    これもまた水際での対応で、
    根本的な解決ではないので他にもっといい方法があればそちらにしたいところですが。

    ですが、Statelessモードにせずに動作させる良い手がかりになりました。
    ありがとうございます。

    straysheep

    2012年12月22日

  • straysheepさん>

    成功しましたか。良かったです。(^o^)
    やはり、ライブラリ直での修正では今後課題が残りますしね。。

    login.phpをなんとかうまく修正する必要がありますね。

    ten

    2012年12月25日

Leave a comment  

name

email

website

Submit comment