Zend_InfoCard

1. 導入

Zend_InfoCard コンポーネントは、 情報カード (Information Cards) の relying-party サポートを実装したものです。 情報カードは、インターネット上でのユーザ識別情報の管理や ウェブサイトのユーザ認証に用いるものです。 最終的にユーザ認証を行う先のウェブサイトのことを relying-party といいます。

情報カードについて、 あるいはインターネット上の識別メタシステムにおける情報カードの重要性については、 IdentityBlog を参照ください。

1.1. 基本的な使用法

Zend_InfoCard の使用法は、 Zend_Auth コンポーネントの一部として Zend_InfoCard 認証アダプタを使用するか、 あるいは単体のコンポーネントとして使用するかのいずれかです。 どちらの場合についても、ユーザから情報カードを受け取るには HTML のログインフォームの中で次のような HTML ブロックを使用します。

<form action="http://example.com/server" method="POST">
  <
input type='image' src='/images/ic.png' align='center'
        
width='120px' style='cursor:pointer' />
  <
object type="application/x-informationCard"
          
name="xmlToken">
   <
param name="tokenType"
         
value="urn:oasis:names:tc:SAML:1.0:assertion" />
   <
param name="requiredClaims"
         
value="http://.../claims/privatepersonalidentifier
         http://.../claims/givenname
         http://.../claims/surname" 
/>
 </
object>
</
form>

この例において、requiredClaims <param> タグで表しているのが、 claim (人の姓名など) と呼ばれる識別情報です。 これは、ウェブサイト ("relying party") が情報カードによる認証を行うために必要となります。

上の HTML をユーザが実行する (クリックする) と、 ブラウザはカード選択プログラムを実行します。 これは、そのサイトの要求を満たす情報カードを表示させるだけでなく、 条件を満たす情報カードが複数存在する場合に好きなものを選択させることができます。 この情報カードは、指定した URL に XML ドキュメントとして POST され、これを Zend_InfoCard コンポーネントで処理することになります。

情報カードは、SSL で暗号化した URL への HTTP POST しかできないことに注意しましょう。 SSL による暗号化を設定する方法については、 ウェブサーバのドキュメントを参照ください。

1.2. Zend_Auth の部品としての使用法

このコンポーネントを Zend_Auth 認証システムと組み合わせて使用するには、 Zend_Auth_Adapter_InfoCard を使用する必要があります (これは、単体で配布されている Zend_InfoCard には存在しません)。 この手法での使用例を以下に示します。

<?php
if (isset($_POST['xmlToken'])) {

    
$adapter = new Zend_Auth_Adapter_InfoCard($_POST['xmlToken']);

    
$adapter->addCertificatePair('/usr/local/Zend/apache2/conf/server.key',
                                 
'/usr/local/Zend/apache2/conf/server.crt');


    
$auth Zend_Auth::getInstance();

    
$result $auth->authenticate($adapter);

    switch (
$result->getCode()) {
        case 
Zend_Auth_Result::SUCCESS:
            
$claims $result->getIdentity();
            print 
"Given Name: {$claims->givenname}<br />";
            print 
"Surname: {$claims->surname}<br />";
            print 
"Email Address: {$claims->emailaddress}<br />";
            print 
"PPI: {$claims->getCardID()}<br />";
            break;
        case 
Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID:
            print 
"The Credential you provided did not pass validation";
            break;
        default:
        case 
Zend_Auth_Result::FAILURE:
            print 
"There was an error processing your credentials.";
            break;
    }

    if (
count($result->getMessages()) > 0) {
        print 
"<pre>";
        
var_dump($result->getMessages());
        print 
"</pre>";
    }

}
?>
<hr />
<div id="login" style="font-family: arial; font-size: 2em;">
<p>Simple Login Demo</p>
 <form method="post">
  <input type="submit" value="Login" />
   <object type="application/x-informationCard" name="xmlToken">
    <param name="tokenType"
          value="urn:oasis:names:tc:SAML:1.0:assertion" />
    <param name="requiredClaims"
          value="http://.../claims/givenname
                 http://.../claims/surname
                 http://.../claims/emailaddress
                 http://.../claims/privatepersonalidentifier" />
  </object>
 </form>
</div>

上の例では、まず最初に Zend_Auth_Adapter_InfoCard のインスタンスを作成して、 カードセレクタから送信された XML データをそこに渡しています。 インスタンスを作成したら、次に SSL 証明書の秘密鍵/公開鍵 ペアを指定する必要があります。 このペアは、HTTP POST を受け取ったウェブサーバで使用しているものです。 これらのファイルを使用して、サーバに送信された情報のあて先の検証を行います。 情報カードを使用するときにはこれらが必要となります。

アダプタの設定がすんだら、あとは Zend_Auth の標準機能を使って情報カードトークンの検証を行い、 getIdentity() で取得した識別情報をもとにユーザの認証を行います。

1.3. Zend_InfoCard コンポーネント単体での使用法

Zend_InfoCard コンポーネントを、 それ単体で使用することも可能です。その場合は Zend_InfoCard クラスを直接操作します。 Zend_InfoCard クラスの使用法は、Zend_Auth コンポーネントと組み合わせて使用する場合とほぼ同じです。 以下に使用例を示します。

<?php
if (isset($_POST['xmlToken'])) {
    
$infocard = new Zend_InfoCard();
    
$infocard->addCertificatePair('/usr/local/Zend/apache2/conf/server.key',
                                  
'/usr/local/Zend/apache2/conf/server.crt');

    
$claims $infocard->process($_POST['xmlToken']);

    if(
$claims->isValid()) {
        print 
"Given Name: {$claims->givenname}<br />";
        print 
"Surname: {$claims->surname}<br />";
        print 
"Email Address: {$claims->emailaddress}<br />";
        print 
"PPI: {$claims->getCardID()}<br />";
    } else {
        print 
"Error Validating identity: {$claims->getErrorMsg()}";
    }
}
?>
<hr />
<div id="login" style="font-family: arial; font-size: 2em;">
<p>Simple Login Demo</p>
 <form method="post">
  <input type="submit" value="Login" />
   <object type="application/x-informationCard" name="xmlToken">
    <param name="tokenType"
          value="urn:oasis:names:tc:SAML:1.0:assertion" />
    <param name="requiredClaims"
          value="http://.../claims/givenname
                 http://.../claims/surname
                 http://.../claims/emailaddress
                 http://.../claims/privatepersonalidentifier" />
   </object>
 </form>
</div>

上の例では、Zend_InfoCard コンポーネントを単体で使用して、ユーザが送信したトークンを検証しています。 Zend_Auth_Adapter_InfoCard の場合と同様、 Zend_InfoCard のインスタンスを作成してから ウェブサーバの SSL 証明書の公開キー/秘密キーペアを設定します。 設定がすんだら、process() メソッドで情報カードの処理を行ってその結果を返します。

1.4. Claims オブジェクトの使用法

Zend_InfoCard の使用方法 (単体で使用するか、あるいは Zend_Auth の一部として Zend_Auth_Adapter_InfoCard 経由で使用するか) にかかわらず、情報カードを処理した結果は Zend_InfoCard_Claims オブジェクトとして返されます。 このオブジェクトには assertions (claims) が含まれます。 これは、ユーザ認証の際にあなたのサイトが出した要求にもとづいて、 ユーザが送信したデータから作成したものです。 上の例で示したように、情報カードの妥当性を確認するには Zend_InfoCard_Claims::isValid() メソッドをコールします。claims そのものを取得するには、 単純に識別子 (givenname など) をオブジェクトのプロパティとして指定してアクセスするか、 あるいは getClaim() メソッドを使用します。

ほとんどの場合においては getClaim() メソッドを使用する必要はありません。 しかし、もし requiredClaims が複数の異なるソース/名前空間からの情報を要求している場合は、 それをこのメソッドで明示的に取り出す必要があります (claim の完全な URI を私、情報カードの中からその値を取得します)。 一般論として、Zend_InfoCard コンポーネントがデフォルトで設定する claim 用 URI は情報カードの中で最もよく用いられるものです。 この場合は単純にプロパティを使用してアクセスすることができます。

検証処理の中で開発者が行わなければならない部分は、 情報カード内の claim の発行元を調べて それが信頼できる情報元かどうかを判定するところです。 これを行うために、Zend_InfoCard_Claims オブジェクトには getIssuer() メソッドが用意されています。 このメソッドは、情報カードの claim の発行元 URI を返します。

1.5. 既存のアカウントへの情報カードの添付

既存の認証システムに情報カードのサポートを追加することもできます。 そのためには、private personal identifier (PPI) を昔ながらの認証アカウントに埋め込み、 最低限の claim である http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier をリクエストの requiredClaims に指定します。この claim が要求されると、 Zend_InfoCard_Claims オブジェクトはそのカード用の一意な識別子を用意します。 これは、getCardID() メソッドによって行います。

情報カードを既存の昔ながらの認証アカウントに添付する例を、 以下に示します。

// ...
public function submitinfocardAction()
{
    if (!isset(
$_REQUEST['xmlToken'])) {
        throw new 
ZBlog_Exception('Expected an encrypted token ' .
                                  
'but was not provided');
    }

    
$infoCard = new Zend_InfoCard();
    
$infoCard->addCertificatePair(SSL_CERTIFICATE_PRIVATE,
                                  
SSL_CERTIFICATE_PUB);

    try {
        
$claims $infoCard->process($request['xmlToken']);
    } catch(
Zend_InfoCard_Exception $e) {
        
// TODO Error processing your request
        
throw $e;
    }

    if (
$claims->isValid()) {
        
$db ZBlog_Data::getAdapter();

        
$ppi $db->quote($claims->getCardID());
        
$fullname $db->quote("{$claims->givenname} {$claims->surname}");

        
$query "UPDATE blogusers
                     SET ppi = $ppi,
                         real_name = $fullname
                   WHERE username='administrator'"
;

        try {
            
$db->query($query);
        } catch(
Exception $e) {
            
// TODO Failed to store in DB
        
}

        
$this->view->render();
        return;
    } else {
        throw new
            
ZBlog_Exception("Infomation card failed security checks");
    }
}

1.6. Zend_InfoCard アダプタの作成

Zend_InfoCard コンポーネントは、 情報カードの標準規格の変化に対応するために モジュラー構造を採用しています。 現時点では、拡張ポイントの多くは未使用ですので無視できますが、 情報カードの実装においてひとつだけ実装すべき点があります。 それが Zend_InfoCard_Adapter です。

Zend_InfoCard アダプタは、 コンポーネント内でコールバックを使用してさまざまな処理を行います。 たとえば、コンポーネントが情報カードを処理する際の Assertion ID の保存や取得などを行います。 受け取った情報カードの assertion ID の保存は必須ではありませんが、 もしそれに失敗すると、リプレイ攻撃によって認証が信頼できないものになる可能性が発生します。

これを避けるためには、 Zend_InfoCard_Adapter_Interface を実装してそのインスタンスを設定してから process() メソッド (単体) あるいは authenticate() メソッド (Zend_Auth アダプタ) をコールしなければなりません。 このインターフェイスを設定するためのメソッドが setAdapter() です。 以下の例では、Zend_InfoCard アダプタを設定してアプリケーション内で使用しています。

class myAdapter implements Zend_InfoCard_Adapter_Interface
{
    public function 
storeAssertion($assertionURI,
                                   
$assertionID,
                                   
$conditions)
    {
        
/* Store the assertion and its conditions by ID and URI */
    
}

    public function 
retrieveAssertion($assertionURI$assertionID)
    {
        
/* Retrieve the assertion by URI and ID */
    
}

    public function 
removeAssertion($assertionURI$assertionID)
    {
        
/* Delete a given assertion by URI/ID */
    
}
}

$adapter  = new myAdapter();

$infoCard = new Zend_InfoCard();
$infoCard->addCertificatePair(SSL_PRIVATESSL_PUB);
$infoCard->setAdapter($adapter);

$claims $infoCard->process($_POST['xmlToken']);