Railsコードリーディング

第5回 OpenIDを実装したソースコードを読もう

倉貫 義人
松村 章弘
TIS株式会社
SonicGarden

2009/6/3

icon OPとして動作するSKIPのコードを読む

 OPとして動作するために必要な実装は、RPを実現するのに比べて非常に複雑です。認証情報を管理するだけでなく、多くの処理が必要となります。

 今回は、正常系の流れの中でも、SKIP独自の部分を中心に見ていくことで全体像をつかみましょう。

 SKIPのOP部分は、masqueradeというRailsで作られたオープンソースのOP実装のソースコードを参考に実装しました。このmasqueradeというアプリケーションは、OpenID ver2.0のプロバイダとしての基本機能を備えているもので、そこから必要な部分を切り出しながらSKIPのOP実装は行いました。

関連リンク:
リンク masquerade
http://github.com/dbloete/masquerade/

 OPとしての動作の流れは、前節のSKIPをRPで動作させた場合と同じになります。その流れの中の、例えばmixiが行ったOPの部分を、SKIPで作り込んだと考えてください。

 任意のRPで認証を求められた際に、OPであるSKIPにリダイレクトされてきますので、自前で管理しているユーザー情報を基に認証処理を行うためのログイン画面の表示を行います。

 ユーザーからのログインのアクションを受けて認証処理を行った後は、適切な認証情報をパラメータに付与した上で、元のRPにリダイレクトして戻す、というのが詳しい流れになります。その部分だけをまとめると以下のようになります。

  1. RPは、OPであるSKIPの認証処理のためのURL(エンドポイントと呼ばれます)にリダイレクトする
  2. OPであるSKIPで、すでにログイン済みなら後処理(4)へ、ログイン前ならログイン画面(3)へリダイレクトする
  3. ログイン画面を表示してユーザーからのログインを処理した後、後処理(4)へリダイレクトする
  4. 認証を要求した元のRPへ、適切な認証情報を付与した上でリダイレクトする

 何度かリダイレクトを繰り返しますが、ユーザーからの受け付けはログイン画面でのログイン処理のみとなります。同一Webブラウザ上で、すでにOPでログイン済みの場合、ユーザーはログインが不要になります。この流れで順にソースコードを追っていきましょう。

 まず1つ目のRPからのリダイレクト処理ですが、この際にRP側はOPのどのURLにリダイレクトすれば良いか分かりません。

 利用者が入力もしくは選択するのは、利用者自身をユニークに識別するOpenID Identifierか、どのOPを使うかということを識別するOP Identifier(例えばhttp://mixi.jp)か、のいずれかだけです。

 前者は利用者にひも付くURL、後者はOPにひも付くURLになってはいますが、そのURLがリダイレクト先とは限りません。エンドポイントとなるURLが分かっていない状態からスタートするのです。

 OpenID ver2.0からは、エンドポイント情報をXRDSと呼ばれるXMLを使ってOPが提供することになっています。XRDSのXMLデータを取得できるためのURIが必要になりますが、それはOP Identifierにリクエストした場合のHTTPレスポンスヘッダに含まれるX-XRDS-Locationというパラメータで指定されます。

 OP側の実装は、これらに対する準備が必要となります。例えば、XRDSのURIを指定している部分のソースコードは以下になります。

app/controllers/platform_controller.rb
25
26
27
28
29
def index
  response.headers['X-XRDS-Location'] = formatted_server_url(:format => :xrds, :protocol => scheme)
  img_files = Dir.glob(File.join(RAILS_ROOT, "public", "custom", "images", "titles", "background*.{jpg,png,jpeg}"))
  @img_name = File.join("titles", File.basename(img_files[rand(img_files.size)]))
end

 26行目がXRDSの位置の指定をしている部分です。/server.xrdsというURIを代入して返すようにしています。次に、そのURIにアクセスした場合に実行されるソースコードを見てみましょう。

app/controllers/servers_controller.rb
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def index
  clear_checkid_request
  respond_to do |format|
    format.html do
      if openid_request.is_a?(OpenID::Server::CheckIDRequest)
        handle_checkid_request
      elsif openid_request
        handle_non_checkid_request
      else
        render :text => _('This is an OpenID server endpoint, not a human readable resource.')
      end
    end
    format.xrds do
      render :layout => false
    end
  end
end

 servers_controller.rbのindexメソッドの拡張子がxrdsの場合の処理は47〜49行目です。単純にビューに処理を渡しているだけです。対応するビューapp/views/server/index.xrds.builderの中でXRDS形式のXMLを生成して返すようになっており、そのXRDSの中で最初のRPからOPへのリダイレクト先となるエンドポイントのURIが提示されています。

 SKIPの場合は、/serverになります。つまり、上記のservers_controller.rbのindexメソッドということになります。このindexメソッドが、拡張子によって2つの意味合いの違う処理を実装しているため複雑になっていますが、RPからOPへリダイレクトされた時に最初に実行されるのは、このindexメソッドということになります。

 正しい認証リクエストの場合は、40行目のhandle_checkid_requestが実行されます。そのメソッドを見てみましょう。

app/controllers/servers_controller.rb
89
90
91
92
93
94
95
96
97
98
99
def handle_checkid_request
  if allow_verification?
    save_checkid_request
    redirect_to proceed_path
  elsif openid_request.immediate
    render_response(openid_request.answer(false))
  else
    save_checkid_request
    redirect_to login_path(:return_to => URI.encode(proceed_path))
  end
end

 このhandle_checkid_requestメソッドでは、ログイン状態に応じてリダイレクト先を判別します。すでに同一Webブラウザでログインが済んでいる場合は、すぐに後処理のURLにリダイレクトしています(92行目)。ログインがまだの場合は、ログイン画面にリダイレクトしています(97行目)。ログイン画面へリダイレクトする際には、ログイン後の戻り先のURLに後処理のURLを指定しています。

 ログイン画面とその処理は非常にシンプルなので割愛して、ログイン後の処理のメソッドを見てみましょう。

app/controllers/servers_controller.rb
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def proceed
  identity = identifier(current_user)
  if INITIAL_SETTINGS['white_list'].include? checkid_request.trust_root
    resp = checkid_request.answer(true, nil, identity)
    props = convert_ax_props(current_user)
    resp = add_ax(resp, props)
    render_response(resp)
  elsif checkid_request.immediate && (sreg_request || ax_fetch_request)
    render_response(checkid_request.answer(false))
  elsif checkid_request.immediate
    render_response(checkid_request.answer(true, nil, identity))
  else
    flash[:error] = _("This site is not allowed")
  redirect_to root_url
  end
end

 proceedメソッドでは、63行目から67行目でホワイトリストとして登録されているRPに対しては情報を付加してリダイレクトを行っています。ホワイトリストに登録されていないRPの場合は、73、74行目のように許可されていないというメッセージを表示し、SKIPのマイページに遷移するという処理を行ないます。

 このホワイトリストによる特定RPへの認証情報を許可する方式がSKIP独自の拡張になっています。ユーザーごとにアプリケーションへの認証情報の引き渡しの許可を確認するようにしてしまうと、社内利用者にとっては操作が煩雑になってしまう点を解決できます。

 また、社内管理者の意図しないアプリケーションで、社員用のSKIPをOPとして使われるのを防ぐために、SKIPの管理者だけが認証情報の引き渡しを許可するアプリケーションをホワイトリストという形で管理するようにしました。

 これによって、利用者はOpenIDを利用していることを意識させないような擬似的なシングルサインオンを実現することができました。

 今回は、比較的新しい技術であるOpenIDをSKIPの中でどのように応用して実装しているかという部分をコードリーディングするという実践的な内容になりました。本記事内で紹介した以外のソースコードも読むことでOpenIDの仕様そのものについての理解も深まることでしょう。

 次回は、SKIPの大きな特徴の1つでもある、閲覧権限を含めたコンテンツ全体からのユニバーサル検索を可能にした、全文検索エンジン「HyperEstraier」との連携について解説する予定です。

next
3/3
 

Index
OpenIDを実装したソースコードを読もう
  Page1
OpenIDでWebの認証を便利に実現
OpenIDについて
SKIPバージョン1.1リリースしました!
  Page2
RPとして動作するSKIPのコードを読む
SKIPにおけるRPの活用シーンと独自の拡張
Page3
OPとして動作するSKIPのコードを読む
index Railsコードリーディング 〜scaffoldのその先へ〜

 Ruby/Rails関連記事
プログラミングは人生だ
まつもと ゆきひろのコーディング天国
 ときにプログラミングはスポーツであり、ときにプログラミングは創造である。楽しいプログラミングは人生をより実りあるものにしてくれる
生産性を向上させるRuby向け統合開発環境カタログ
Ruby on Rails 2.0も強力サポート
 生産性が高いと評判のプログラミング言語「Ruby」。統合開発環境を整えることで、さらに効率的なプログラミングが可能になる
かんたんAjax開発をするためのRailsの基礎知識
Ruby on RailsのRJSでかんたんAjax開発(前編)
 実はAjaxアプリケーション開発はあなたが思うよりも簡単です。まずはRuby on Railsの基礎知識から学びましょう
Praggerとnetpbmで作る画像→AA変換ツール
Rubyを使って何か面白いものを作ってみよう!
 一般的な画像をアスキーアートに変換するツールを作ってみる。さらに出力にバリエーションを持たせてみよう
コードリーディングを始めよう
Railsコードリーディング〜scaffoldのその先へ〜(1)
 優れたプログラマはコードを書くのと同じくらい、読みこなす。優れたコードを読むことで自身のスキルも上達するのだ
  Coding Edgeフォーラムフィード  2.01.00.91


Coding Edge フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間