OpenIDとRails:Authentication 2.0はじめにOpenIDは、ユーザー認証および識別サービス分野に大きな変革をもたらすサービスであり、フレームワークであり、プロトコルでもあります。2004年にBrad FitzpatrickによってスタートしたOpenIDですが、今ではAOL、Google、IBM、Microsoft、VeriSign、Yahoo!などの巨大インターネット組織からサポートされるほどのフレームワークに成長しています。OpenIDは、Webサイトのための信頼性に優れたオープンな分散ユーザー認証を実現する仕組みであり、Web開発者は認証コードを書く手間から解放されます。本稿ではOpenIDの概要と、Webサイト開発にとってのOpenIDの利点について説明します。また、OpenIDをRuby on Rails 2.0フレームワークに組み込む方法について例を挙げて説明します。 必要な環境
Web全体で共通するユーザー識別情報Webアプリケーションにおける認証とは、ユーザーの識別情報を検証し、アプリケーションにアクセスしようとしているユーザーが「本人」であることを確認するプロセスです。最も一般的なのは、ユーザー名とパスワードを使用する認証方式です。この方式では、ユーザー名はそのユーザーの識別情報を表し、パスワードは検証トークンを表します。この検証トークンにより、その所有者以外の人物は、関連付けられているユーザー名の本人であることを主張できないことが保証されます。ユーザーの識別情報を検証する必要があるほとんどのWebサイトではこの認証方式を採用しており、サイト独自のロジックや検証コンポーネントを実装しています。 ところが、ユーザー名とパスワードによる認証方式ではユーザー資格情報があちこちで必要になるため、最終的には銀行口座や電子メール、ブログ、会社のWebサイトなど、さまざまなサイトで数十個のユーザー名とパスワードを使用するという結果になります。 OpenIDは、このような資格情報の散在を解決します。OpenID Webサイトにも記載されているように、OpenIDは「インターネット上で使用する単一のデジタル識別情報(a single digital identity across the Internet)」であり、さまざまなWebサイトでいくつものユーザー名を使用しなくてもよくなります。OpenIDはユーザーの資格情報を1箇所で管理し、他のWebサイトでユーザーの識別情報を検証する必要がある場合は、管理しているWebサイトからユーザーの資格情報を照会できます。 OpenIDのエンティティと認証フローOpenIDサービスは、次のエンティティで構成されています。これらのエンティティは相互に通信します。
図1および図2に、従来の認証方式とOpenID認証方式を示します。 図1 従来の認証方式。ユーザーは、Webサイトごとに異なる資格情報を使用します。
![]() 図2 OpenID認証方式。ユーザーは、資格情報を1つだけ使用します。この資格情報は他のWebサイトと共有されます。
![]()
OpenIDの最新の仕様(バージョン2.0)では、複雑さを考慮してProvider Discoveryの概念を取り入れています。最新の仕様についてあまり知らなくても、WebサイトとOpenIDは適切に統合できます。図3に、上記の一連の手順を簡単に示します。 図3 OpenID認証フロー。ユーザーは、Webサイトごとに異なる資格情報を使用します。
![]() RubyとOpenIDの接続OpenID対応のWebアプリケーションを作成する具体的な例を見ていきたいと思いますが、これにはRuby on Rails 2.0に基づく従来型のWebサイトが必要です。これから紹介する例では、まず基本的なアプリケーションを作成し、その後で資格情報チェック機能とユーザーログインを追加して、このアプリケーションを強化します。この基本的なアプリケーションは、Webベースの簡単なTODOリストです。OpenIDの組み込み作業に集中できるように、非常にシンプルなアプリケーションになっています。アプリケーション作成手順
アプリケーションへOpenIDを組み込む次に、このアプリケーションにOpenIDを組み込んで、以下のことができるようにします。
インテリジェントな認証システムの実現ここからはアプリケーションコードを修正し、ログインコントローラーを実装して、認証システムを構築していきます。アプリケーションコードの修正
ログインコントローラーによる認証の仕組みログインコントローラを実装することにより、ログインの仕組みの最重要部を確認できます。端末で次のコマンドを実行します。# script/generate controller login リスト1 ログインコントローラ
require 'ostruct' class LoginController < ApplicationController def index redirect_to :controller => "todos" if session[:user_id] end def login if using_open_id? authenticate else flash[:error] = "You must provide an OpenID URL" redirect_to :action => "index" end end def logout session[:user_id] = nil redirect_to :action => "index" end protected def authenticate(identity_url = "") authenticate_with_open_id( params[:openid_url], :required => [:nickname, :email]) do |result, identity_url, registration| if result.successful? @user = OpenStruct.new @user.identity_url = identity_url @user.nickname = registration["nickname"] @user.email = registration["email"] session[:user_id] = @user jumpto = session[:jumpto] || { :controller => "todos" } session[:jumpto] = nil redirect_to(jumpto) else flash[:error] = result.message redirect_to :action => "index" end end end def root_url openid_url end end indexメソッドindexメソッドは、ユーザーが既にログインしている場合はユーザーをWebサイトのメインページ(http://localhost:3000/todos)にリダイレクトし、まだログインしていない場合はログインページ(http://localhost:3000/login)に転送します。まだログインページのビューがありませんので、次のコードを使用して「app/views/login/index.html.erb」ファイルを作成します。<% if flash[:error] -%>
<%= flash[:error] %>
<% end -%>
<% form_tag :controller => "login" , :action => "login" do |f| -%>
<label for="openid_url" >
OpenId URL:
</label>
<%= text_field_tag :openid_url -%>
<%= submit_tag "Login" -%>
<% end -%>
openid_urlというラベルは慣用的なラベルで、これがあることでブラウザはユーザーのOpenID IdentifierをOpenID対応のWebサイト全体で記憶できます。loginメソッドとlogoutメソッドloginメソッドはログイン処理を扱い、logoutメソッドはアプリケーションからログアウトしたユーザーのデータをセッションからすべて削除します。using_open_id?関数はruby-openidライブラリに属する関数で、ユーザーがログインを開始中または終了中であるかどうかを検出します。図3に示すように、ログイン処理ではWebサイトに対して呼び出しが2回行われます。1回目の呼び出しにはログイン処理を開始するOpenID Identifierが含まれ、2回目の呼び出しにはOpenID Providerから返されたユーザー資格情報が含まれます。従って、loginメソッドは2回呼び出され、using_open_id?関数は両方の発生を検出します(これについては、後で詳しく説明します)。図3 OpenID認証フロー。Webサイトに対して2回の呼び出しが行われます。
![]() authenticate関数authenticate関数には認証ロジックのほとんどが記述されています。これは、ruby-openidライブラリのauthenticate_with_open_id関数に委任されます。この関数はログイン処理が正常に完了したかどうかに関係なく、その完了時(つまり、Webサイトへの2回目のコールバックの後)に実行されるブロックを受け付けます。このブロックには、次の3つのパラメータを指定できます。
:optionalディレクティブではなく:requiredディレクティブを使用して、ユーザーの資格情報以外にもニックネームと電子メールアドレスを要求しています。OpenIDによりSREGでも認証機能が向上します。従来の仕組みを採用したWebサイトでは、ユーザーは登録フォームに必要事項をすべて入力する必要があり、サードパーティごとに個人データを何度も繰り返し指定しなければなりません。 最後に、 authenticateメソッドは:user_idセッション変数にユーザーデータをすべて格納し、redirect_to(:jumpto)文によってユーザーを最初の要求ページにリダイレクトします。routes.rbファイルとroot_urlメソッドの設定前述したように、loginメソッドは2回呼び出されます。2回目の呼び出しでは、OpenID Providerからユーザーブラウザへのリダイレクトが指示されます。このリダイレクトを正しく行うには、最後に2つの処理を実行する必要があります。まず、次のように「routes.rb」構成ファイルに適切な経路を定義する必要があります(名前付き経路を使用していることに注意してください)。 map.openid "login", :controller => "login" , :requirements => { :method => :get } root_urlメソッドで参照する必要があります。def root_url openid_url end 最後の処理が完了すると、適切に認証され識別されたユーザーのみ、アプリケーションは受け付けるようになります。また、追加情報を使用してユーザーにさらに便利な機能を提供することもできます。 例えば、ユーザーが新しいTODO項目を作成するときに、ユーザー名と電子メールアドレスを既に入力しておくことができます。これを行うには、「todos_controller.rb」ファイルを再度開き newメソッドを次のように修正します。def new @todo = Todo.new @todo.person = session[:user_id].nickname @todo.email = session[:user_id].email respond_to do |format| format.html # new.html.erb format.xml { render :xml => @todo } end end その他の多くの機能今までにRailsを使用した経験がある方ならば、本稿で説明している実装では従来の既存の認証フレームワーク(restful_authenticationまたはact_as_authenticated)をまったく使用していないことに気付くかもしれません。これには前述したように、OpenID認証は既存の認証ロジックに依存しないからです。また、Railsのopen_id_authenticationプラグインへの依存を避けたり、柔軟性を高めたりする必要がある場合は、ruby-openidライブラリに直接アクセスすることもできます。リスト2に別バージョンのログインコントローラを示します。 このログインコントローラでは、関数呼び出しとパブリッククラスを直接使用しています(コードが長くなっています)。さらに大きな違いとして、このログインコントローラではアプリケーションとOpenID Providerとのハンドシェークプロセスで使われる一時的なデータを、データベースではなくファイルシステムに格納しています。 リスト2 別バージョンのログインコントローラ
require "openid" require 'user' class OpenID::Consumer attr_accessor :referer end class LoginController < ApplicationController def login if session[:user] @login = session[:user] render :action => 'status' , :layout => false return end if params[:login] @login = OpenID::OpenidUser.from_params(params[:login]) if @login.validate store_dir = OPENID_STORE_DIR store = OpenID::FilesystemStore.new(store_dir) consumer = OpenID::Consumer.new(session, store) consumer.referer = @request.env['HTTP_REFERER'] req = consumer.begin(@login.openid) case req.status when OpenID::FAILURE @login.errors.add(:openid,"Unable to find OpenID server") when OpenID::SUCCESS # simple registration procedure to extract # the email, nickname and fullname (if available) req.add_extension_arg('sreg','required','email,nickname') req.add_extension_arg('sreg','optional','fullname') @redirect_url = req.redirect_url(BASE_URL, url_for( :action => 'complete')) # save the consumer for later use session[:consumer] = consumer session[:temp_login] = @login redirect_to @redirect_url else # should never arrive here raise "Unreachable code" end end end render :action => 'status' , :layout => false end def complete if session[:user] @login = session[:user] render :action => 'status' , :layout => false return end consumer = session[:consumer] session[:consumer] = nil # consume consumer if consumer.nil? render :action => 'status' , :layout => false return end res = consumer.complete(params) if res.status == OpenID::FAILURE flash[:login_notice] = "Login has been rejected" elsif res.status == OpenID::SUCCESS sreg = res.extension_response('sreg') if sreg.length > 0 # finalize login @login = session[:user] = session[:temp_login] @login.email = sreg['email'] @login.nickname = sreg['nickname'] @login.fullname = sreg['fullname'] session[:temp_login] = nil end elsif res.status == OpenID::CANCEL flash[:login_notice] = "Login has been canceled." else # unknown response type flash[:login_notice] = "OpenID provider has returned" + " an unknown response type" end redirect_to consumer.referer end def logout session[:user] = session[:temp_login] = nil render :action => 'status', :layout => false end end OpenIDは多くの可能性を秘めているOpenIDサービスには明確な利点があります。開発者の立場からは、コードの重複や複雑さが軽減し管理上の手間が省けます。ユーザーの立場からは、より統一感のあるWebエクスペリエンスが得られます。ただし、本稿ではOpenIDの広大な世界についてごく表面的に説明したにすぎません。新しいOpenID 2.0の仕様、サービスのディスカバリとデリゲーションの仕組み、XRDS(Extensible Resource Descriptor Sequence)フォーマット、Stateless Relying Partyなどまだ紹介していない機能が数多くあります。 また、ruby-openidライブラリについても簡単にしか説明していません。例えば、ruby-openidライブラリは、前述したRelying Partyのクライアントライブラリではなく、Rails OpenID Providerのセットアップにも対応しています。 OpenIDイニシアチブはオープンであるため、必要があればいくらでも深いところまで掘り下げていくことができるのです。 著者紹介Riccardo Govoni(Riccardo Govoni)
2003年からJ2EE開発者としてイタリアの金融サービス会社に勤務。ソフトウェアアーキテクトとして従来の銀行システムのWebフロントエンドの設計に従事。物理学修士。SCJP(Sun Certified Java Programmer)資格を持つ。
関連記事 最新トップニュース
|
【ケータイ USA】新しい iPod は来週火曜に発表されるだろう(9月6日 13:00)
ソフトバンクモバイル、8月の純増数は約16万件――携帯電話契約数に関する速報(9月5日 14:40)
なぜ勝った? 世界No.1シェアをつかんだ“Windows”(9月5日 11:00)
Apple が『iPod』関連の発表を準備中、内容は如何に(9月4日 12:40)
TCA、8月度の携帯契約数を発表――ソフトバンクが16か月連続純増 No.1 に(9月5日 18:00)
私の周りは“geek out”している人ばかり(9月5日)
|