いますぐ使えるOpenID

第3回OpenIDプロトコルの特徴-DiscoveryとSREG

前回のおさらい

前回はRPのサンプルをRails上で動作させました。 この時に使用したRailsのアクションをまとめておきます。

ConsumerController#begin
OpenIDリクエストを生成するRailsのアクション。
ConsumerController#complete
OpenIDレスポンスを受信するRailsのアクション。

OpenIDの一番の特徴は、分散型である(複数のOPが存在する)ことです。 そのためDiscoveryなどの、OpenID特有の処理があります。 ですので今回は、OpenIDプロトコルの特徴的な箇所を、もう少し深く見ていきましょう。 取り上げるのはDiscoveryとSREGの2つです。

Discovery
利用者が入力したOpenIDアカウント名(User-Supplied Identifier)を元にOPのログイン画面(OP Endpoint URL)を見つける。
SREG
OpenIDの拡張領域を使って利用者のメールアドレスなどをOPとRPで交換する。

Discovery…OpenIDアカウント名からOPのログイン画面を見つける

前回のサンプルでは、はじめに利用者がRPのログイン画面にOpenIDのアカウント名を入力しました。 このときOpenIDライブラリを使って、アカウント名からOpenIDリクエストを作成しています。

    # ユーザが入力したopenid_identifierを元にOpenIDリクエストを作成 
17  oidreq = consumer.begin(params[:openid_identifier])

consumerはOpenID::Consumerクラスのオブジェクトで、oidreqはOpenID::Requestクラスのオブジェクトでした。 OpenID::Consumerオブジェクトのbeginメソッドを呼び出してOpenID::Requestオブジェクトを取得します。 OpenID::Requestオブジェクトのredirect_urlメソッドで、OPのログイン画面のURLを取得できます。

    # 利用者をOPのログイン画面(OP EndPoint)へと誘導する
47  redirect_to oidreq.redirect_url(realm, return_to, params[:immediate]) 

ここで重要なのは、利用者が入力するOpenIDアカウント名のURLと、利用者をOPのログイン画面へと誘導するためのURLは異なることです。 OpenIDの仕様では、利用者を誘導先となるOPのURLのことを、OPのエンドポイントURL(OP Endpoint URL)と呼びます。

はてな
利用者が入力したOpenIDアカウント名(User-Supplied Identifier)
http://www.hatena.ne.jp/kmachu/
OPのエンドポイントURL(OP Endpoint URL)
http://www.hatena.ne.jp/openid/server
Yahoo!
利用者が入力したOpenIDアカウント名(User-Supplied Identifier)
yahoo.com
OPのエンドポイントURL(OP Endpoint URL)
https://open.login.yahooapis.com/openid/op/auth

OpenIDでは不特定多数のOPが存在するため、事前に「はてなの場合はこのURL, Yahoo!の場合はこのURL」などとOP のエンドポイントURLを覚えておくことはできません。 OPのエンドポイントURLを動的に見つける必要があります。 前回のサンプルでは、OpenID::Consumerのbeginメソッドを呼び出したときにOPのURLを見つける処理をOpenIDライブラリが実行しています。 これが「discovery」です。 discoveryにはいくつかの方法があり、どの方法が使えるかはOP次第です。

  • HTMLの中にOPのエンドポイントURLを記述する
  • YadisとXRDSを用いる

概念を説明するよりも、具体的な例を見た方が分かりやすいと思います。

discoveryコマンドを使って認証サーバを見つけよう

ruby-openidライブラリには、OpenIDのdiscoveryを試すことができるdiscoverスクリプトが付属しています。 discoverは以下の場所にあります。

ruby-openid-2.0.4/examples/discover

まず、はてなのOpenIDアカウント名を入力してみましょう。

$ ./discover http://www.hatena.ne.jp/kmachu/
==================================================
Running discovery on http://www.hatena.ne.jp/kmachu/
 Claimed identifier: http://www.hatena.ne.jp/kmachu/
 Discovered services:
  1.
     Server URL  : http://www.hatena.ne.jp/openid/server 
     Type URIs:
       * http://openid.net/signon/1.1

Claimed identifierが入力したOpenIDアカウント名です。 discoverコマンドはこのURLを元にdiscoveryを行い、OPのエンドポイントURLであるhttp://www.hatena.ne.jp/openid/serverを見つけています。 同時に、このOPが対応しているOpenIDのバージョンが1.1であることも認識しています。

Yahoo!でも試してみましょう。 Yahoo!の場合は利用者のOpenIDアカウント名ではなくyahoo.comとだけ入力すればよかったですね。 このyahoo.comのことをOP Identifierと言います。

$ ./discover yahoo.com
Running discovery on yahoo.com
 Claimed identifier: http://www.yahoo.com/
 Discovered services:
  1.
     Server URL  : https://open.login.yahooapis.com/openid/op/auth 
     Type URIs:
       * http://specs.openid.net/auth/2.0/server
       * http://specs.openid.net/extensions/pape/1.0

はてなの例と同じように、OPのエンドポイントURLであるhttps://open.login.yahooapis.com/openid/op/authを取得できました。 さらに、OpenID 2.0に対応していることと、PAPEという拡張領域に対応していることも分かります。 拡張領域については後ほど説明します。

なお、2007年4月時点で、はてなではOpenID2.0に対応していないため、OP Identifierを入力 してもdiscoveryに失敗します。

$ ./discover http://www.hatena.ne.jp/
================================================== 
Running discovery on http://www.hatena.ne.jp/
 Claimed identifier: http://www.hatena.ne.jp/
 No OpenID services found

HTMLからOPのURLを取得する

はてなもYahoo!もdiscoveryによってOPのエンドポイントURLを見つけることができました。 ですが、はてなとYahoo!では、具体的な見つける手法は異なります。 もう少し詳しく見てみましょう。

はてなの場合は、利用者が入力したURLに存在するHTMLの中に、OpenIDのサーバ名がそのまま書かれています。

curlコマンドを使ってHTMLを取得し、openidという文字が含まれる行だけを抜き出してみます。

$ curl http://www.hatena.ne.jp/kmachu/ | grep openid
<link rel="openid.server" href="http://www.hatena.ne.jp/openid/server" /> 

HTML文書に含まれるlink要素として、openid.serveのURLがhttp://www.hatena.ne.jp/openid/serverであることが記述されています。 この記述方法は、OpenIDのバージョン1のフォーマットです。 そのため、discoveコマンドは、はてなの OPがOpenID 1.1対応と判定したのです。

なお、OpenIDのバージョン1.1では、HTMLに記述する方法がdiscoveryの唯一の方法です。

Yadis+XRDSからOPのURLを取得する

次にYahoo!で試してみましょう。 同じようにcurlコマンドを使ってHTMLを取得し、openidという文字が含まれる行を探してみます。

$ curl http://www.yahoo.com/ | grep openid

結果は空でした。HTMLの中にはOpenIDの記述が書かれていないようです。 実はYahoo!では、OPのエンドポイントURLはHTML文書とは別の場所に置かれたXRDSというXML文書に書かれています。 XRDS文書の場所は、HTTPレスポンスヘッダに含まれる「X-XRDS-Location」というパラメータで指定されます。

$ curl --head http://www.yahoo.com/
HTTP/1.1 200 OK
Date: Thu, 27 Mar 2008 23:18:13 GMT
X-XRDS-Location: http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds 
Last-Modified: Thu, 27 Mar 2008 23:04:22 GMT
Content-Length: 9616
Connection: close
Content-Type: text/html; charset=utf-8

指定されたURLにアクセスするとXRDS文書が取得できます。 このXMLの中に、OPのエンドポイントURLが記述されています。

$ curl http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds 
<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS
    xmlns:xrds="xri://$xrds"
    xmlns:openid="http://openid.net/xmlns/1.0"
    xmlns="xri://$xrd*($v*2.0)">
  <XRD>
    <Service priority="0">
      <Type>http://specs.openid.net/auth/2.0/server</Type>
      <Type>http://specs.openid.net/extensions/pape/1.0</Type>
      <URI>https://open.login.yahooapis.com/openid/op/auth</URI>
    </Service>
  </XRD>
</xrds:XRDS>

このように、Yahoo!ではOPのエンドポイントURLを取得するために2つのステップを踏みます。

  1. 利用者が入力したURLからXRDS文書のURLを取得する
  2. XRDS文書を取得し、XRDS文書中に書かれているOPのエンドポイントURLを取得する

ステップが一つ増えているので、ちょっと複雑に感じるかもしれませんが、HTML中にURLを書くのではなく別のXML とすることで、より柔軟な記述が可能となっています。 なお、URLからXRDS文書を見つける手順はOpenIDとは別のYadisという仕様で決められています。

discoveryのまとめ

動的にOPのエンドポイントURLを見つけるdiscoveryには、以下の方法がありました。

  • HTML文書に書かれたOPのエンドポイントURL取得する
  • Yadisプロトコルを使ってXRDS文書を探し、XRDS文書に書かれたOPのエンドポイントURLを取得する
図1

YadisとXRDS文書については、以下のサイトの資料が参考になります。

参考サイト
URLhttp://d.hatena.ne.jp/ZIGOROu/20080317/1205725544

OpenIDの拡張領域

OpenIDは拡張領域(Extension)を定義できるようになっています。 拡張領域を使うことで、OpenIDのアカウント名を渡すだけでなく他の情報のやり取りも可能になります。 以下のような拡張領域が定義されています。

PAPEはOPにおける利用者の認証レベルを表すものです。 RPがOPに対して、例えばパスワードと一緒にデジタル証明書を用いて利用者を認証することを要求することができます。 RPが重要なデータを扱うサービスの場合は、OPに対して強固な認証を要求するといった使い方が想定されています。 ただし、本当にRPが要求したレベルの認証をOPが行っているかどうかは、RPには分かりません。 OPが「デジタル証明書で利用者を認証しました」とウソをつきながら、実はパスワードで認証しているということもあり得ます。 そのため、PAPEは信頼できるOPに対して使うことになるでしょう。

SREGとAXは、どちらも利用者の名前やメールアドレスなどの属性情報をOPに対して要求するものです。 AXはより汎用的な属性情報を扱えるようになっています。 これらの拡張を使うことで、ユーザ登録時の情報の入力を省くことができるというメリットがあります。 今回は、SREGを用いて利用者の名前とメールアドレスを取得するサンプルを試してみます。

SREGを使って利用者の名前とメールアドレスを取得する

前回動かしたRailsのサンプルには、SREGを使うためのインタフェースが用意されています。 ですが、利用者の情報を他のサイトに伝えるということから、SREGに対応しているOPはそれほど多くありません。 discoverコマンドを使うことで、OPがSREGに対応しているかどうかを確認することができます。 Yahoo!やはてなはSREGに対応していないので、ここではmyopenid.comというSREGに対応したOPを使います。 discoverコマンドで確認すると、OpenID2.0とSREG1.0に対応していることが分かります。

$ ./discover http://myopenid.com
==================================================
Running discovery on http://myopenid.com
 Claimed identifier: http://www.myopenid.com/
 Discovered services:
  1.
     Server URL  : http://www.myopenid.com/server
     Type URIs:
       * http://specs.openid.net/auth/2.0/server 
       * http://openid.net/sreg/1.0

筆者はあらかじめmyopenid.com にアカウントを作成し、ニックネームとメールアドレスも登録しておきました。

前回も利用したRailsのサンプルを起動して、OpenIDのアカウント名を入力する画面にて「Request registration data」というチェックボックスをオンにするとOpenIDの拡張領域にSREGのパラメータが設定されます。

図2

コードを見てみましょう。SREGの要求パラメータはOpenID::SReg::Requestクラスを使って管理します。 OpenID::SREG::Requesのオブジェクトを作り、フィールドに要求する値を設定します。 そして、 OpenID::Requestクラスのオブジェクトに対して、add_extensionメソッドを呼び出してOpenID::SReg::Requestオブジェクトを登録します。 この例では、メールアドレスとニックネームを必須項目として要求しています。

25  sregreq = OpenID::SReg::Request.new
26  # required fields
27  sregreq.request_fields(['email','nickname'], true)
28  # optional fields
29  sregreq.request_fields(['dob', 'fullname'], false)
30  oidreq.add_extension(sregreq)

OPであるmyopenid.comのログイン画面では、ログイン後にメールアドレスやニックネームをRPへと渡してよいかの確認が行われます。

ここでmyopenid.comが面白いのは、複数の登録データを「ペルソナ」として選択できるようになっていることです。 Yahoo!がRPに渡すIDを選べるようになっていたように、myopenid.comではRPに渡すメールアドレスやニックネームを選ぶことができます。

図3

ログインが終わりOPからRPへと戻ってくると、OPに登録していたニックネームやメールアドレスがRPの画面に表示されています。 これによってRP でのユーザ登録時の情報を入力する手間を省くことができます。

図4

ソースコードを見てみましょう。 まず、OpenIDのレスポンスを管理するOpenID::Responseオブジェクト(oidresp)からOpenID::SReg::Responseオブジェクトを生成します。

    # OpenID::Response から SREG レスポンスのオブジェクトを生成
71  sreg_resp = OpenID::SReg::Response.from_success_response(oidresp) 

OPから送られてきたデータは、ハッシュ形式で取り出すことができます。 例えば、メールアドレスはsreg_resp.data['email']で取得できます。

    # SREG レスポンスのキーと値を取得
77  sreg_resp.data.each {|k,v|
78   sreg_message << "
#{k}: #{v}" 79 }

SREでは、OPは利用者から預かった情報をRPへ渡すことになるので、慎重に利用する必要があるでしょう。 特に、RPやOPにおいてログイン画面などで利用者に対する周知をしっかり行わないと、⁠知らないうちに個人情報が漏れた!」と誤解されてしまうかもしれません。 そのためか、多数の利用者を抱えるOPにて、SREGを提供しているところはまだあまりありません。

しかし、SREGやAXは可能性を秘めている仕組みですので、面白い使い方が出てくることを期待したいです。

終わりに

今回はOpenIDの内部の仕組みとして、利用者が入力したOpenIDアカウント名(User-Supplied Identifier)からOPのエンドポイント(OP Endpoint URL)を見つけるdiscoveryや、拡張領域を使ってメールアドレスなどを交換するSREGについて説明しました。 内部の仕組みは複雑なところがありますが、これらの面倒な処理はOpenIDライブラリが担当してくれます。

次回はいよいよ、OpenIDを使ったサービスを作ってみることにしましょう。

おすすめ記事

記事・ニュース一覧