japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2008年9月6日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー2008年6月27日 11:00

OpenIDとRails:Authentication 2.0

海外海外internet.com発の記事
  • このエントリーを含むはてなブックマーク
  • この記事をクリップ!
  • Buzzurlにブックマーク
  • Yahoo!ブックマークに登録
  • newsing it!

はじめに

 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サービスは、次のエンティティで構成されています。これらのエンティティは相互に通信します。

  • Provider
    ユーザーの資格情報を保管し、別のWebサイトからのユーザー認証の照会に使われる標準APIを公開します。一般的な無償のProviderとしてmyOpenIDVeriSignがあります。
  • Relying Party
    ユーザーの識別情報の検証を必要とするWebサイトです。
  • End User
    Webサイトで自分自身の認証を試みるユーザーです。
 OpenIDでは、ユーザーはOpenID Identifierによって一意に識別されます。このOpenID Identifierは、一般的にはプレーンURLです(これを固有のユーザー名と見なします)。このOpenID Identifierには通常、ユーザーの資格情報を保管しているProvider(例えば、yourname.myopenid.com)が指定されています。

 図1および図2に、従来の認証方式とOpenID認証方式を示します。

図1 従来の認証方式。ユーザーは、Webサイトごとに異なる資格情報を使用します。
図1 従来の認証方式。ユーザーは、Webサイトごとに異なる資格情報を使用します。
図2 OpenID認証方式。ユーザーは、資格情報を1つだけ使用します。この資格情報は他のWebサイトと共有されます。
図2 OpenID認証方式。ユーザーは、資格情報を1つだけ使用します。この資格情報は他のWebサイトと共有されます。
 これらの図に示すように、OpenID認証フローはデータベースを基にした従来の認証方式とは若干異なります。OpenID認証は、次の手順にまとめることができます。

  1. あるWebサイトで、ユーザーが自分自身の認証を希望します。
  2. ユーザーは、このWebサイトにOpenID Identifierを送信します。
  3. Webサイトは、受け取ったIdentifierを正規のURL(http://yourname.myopenid.comなど)に変換します。
  4. Webサイトが、生成されたURLをブラウザにリダイレクトします。このURLが示す場所で、ユーザーはOpenID Providerに対して自分自身の認証を要求できます。
  5. OpenID Providerが、ユーザー名とパスワードを使用する従来の認証方式、または証明書交換による高度な認証方式によって、ユーザーを認証します。
  6. 認証が正常に完了すると、OpenID Providerは最初のWebサイトに、ブラウザと検証済みのユーザー資格情報をリダイレクトします。
  7. 最初のWebサイトは、この資格情報を使用してユーザーを識別し、そのユーザーにサービスを転送します。
  8. 認証が正常に完了しなかった場合、OpenID Providerは最初のWebサイトにブラウザとエラーキーをリダイレクトします。最初のWebサイトは、このエラーキーを使用して問題を処理できます。
 セキュリティを保証し、識別情報のスプーフィング(なりすまし)を防止するために、WebサイトとOpenID Providerはさらにいくつかの処理を実行します。もっとも、OpenIDクライアントライブラリを使用するのであれば、Web開発者はこれらの処理の詳細を知る必要はなく、すべての処理はライブラリによって透過的に行われます(次の節に示す例では、OpenIDクライアントライブラリを使用しています)。

 OpenIDの最新の仕様(バージョン2.0)では、複雑さを考慮してProvider Discoveryの概念を取り入れています。最新の仕様についてあまり知らなくても、WebサイトとOpenIDは適切に統合できます。図3に、上記の一連の手順を簡単に示します。

図3 OpenID認証フロー。ユーザーは、Webサイトごとに異なる資格情報を使用します。
図3 OpenID認証フロー。ユーザーは、Webサイトごとに異なる資格情報を使用します。

RubyとOpenIDの接続

 OpenID対応のWebアプリケーションを作成する具体的な例を見ていきたいと思いますが、これにはRuby on Rails 2.0に基づく従来型のWebサイトが必要です。これから紹介する例では、まず基本的なアプリケーションを作成し、その後で資格情報チェック機能とユーザーログインを追加して、このアプリケーションを強化します。この基本的なアプリケーションは、Webベースの簡単なTODOリストです。OpenIDの組み込み作業に集中できるように、非常にシンプルなアプリケーションになっています。

アプリケーション作成手順

  1. MySQLインスタンスをローカルで実行していることを確認します。このMySQLインスタンスに接続し、次のコマンドを実行して開発用データベースを作成します。
    create database openid_development ;
    このデータベースにはアプリケーションで使用するデータと、OpenID Providerとの通信に必要な追加テーブルが格納されます。
     
  2. 端末を開き、次のコマンドを使用してRailsアプリケーションを新規作成します。
    # rails -d mysql openid
     
  3. すべて正常に機能していることを確認するためにscript/serverコマンドを実行し、「http://localhost:3000/」上のアプリケーションにアクセスします。
  4. 以下のコマンドを実行して、TODO項目を管理するための基本的な枠組みを作成します。各TODO項目には所有者(名前と電子メールで識別)、開始日と終了日、および説明を割り当てます。
    # script/generate scaffold Todo \
    person:string email:string start:date end:date description:text
    # rake db:migrate
    非常にシンプルですが、このサンプルアプリケーションはTODOリストを問題なく正常に処理します。

アプリケーションへOpenIDを組み込む

 次に、このアプリケーションにOpenIDを組み込んで、以下のことができるようにします。

  • アプリケーションでログイン画面を表示する。
  • OpenID Providerが受け取る資格情報の一部を再利用して、TODO項目作成ページを拡張する。
  1. ruby-openidライブラリをインストールします。このライブラリには、アプリケーションとリモートのOpenID Provider間で通信を行うために必要なクライアントコードがすべて含まれています。このライブラリは、rubygemsを使用してインストールできます。
    # sudo gem install ruby-openid
    インストールしたgemを調べると、ライブラリのバージョンに応じて次のように表示されます。なお、バージョンが異なっていても本稿での作業に支障はありません。
    # gem list --local
    ruby-openid (2.0.4, 1.1.4)
    ruby-yadis (0.3.4)
    ...
    rails (2.0.2)
    ... other installed gems ...
  2. openid_authenticationプラグインをインストールし、Railsと新しくインストールしたruby-openidライブラリを統合します。このプラグインは、いくつかのデータベーステーブルがないと適切に機能しないため、rakeコマンドを実行して必要なテーブルを移行してください。
    # script/plugin install open_id_authentication
    # rake open_id_authentication:db:create
    # rake db:migrate
 これで必要な要素をすべて用意できました。次のページからは認証システムを構築していきます。

インテリジェントな認証システムの実現

 ここからはアプリケーションコードを修正し、ログインコントローラーを実装して、認証システムを構築していきます。

アプリケーションコードの修正

  1. 「app/controllers/application.rb」ファイルを開き、次のauthorizeメソッドを追加します。
    def authorize
      unless session[:user_id]
        flash[:notice] = "Please log in"
        # save the URL the user requested so we can hop back to it
        # after login
        session[:jumpto] = request.parameters
        redirect_to(:controller => "/login", :action => "index")
      end
    end
    このメソッドは、有効な資格情報がないと実行できない処理をユーザーが要求したときに、その要求を中断してログイン画面にリダイレクトします。
     
    また、このメソッドは、要求パラメータをセッションの:jumptoシンボルに格納してログイン画面を生成します。ユーザーはログインするとすぐに、最初に要求したURLに転送されます。
     
  2. 次に、枠組みと共に作成された「todos_controller.rb」を修正する必要があります。下記のように、before_filterを追加します。
    class TodosController < ApplicationController
      before_filter :authorize,
        :only => [ :new , :edit, :create, :update, :destroy]
    
      ... rest of the controller as before ...
    end
    この処理により、TODO項目を作成または修正する操作には、正常にログインしてからでないとアクセスできなくなります。

ログインコントローラーによる認証の仕組み

 ログインコントローラを実装することにより、ログインの仕組みの最重要部を確認できます。端末で次のコマンドを実行します。

# script/generate controller login
 生成された「app/controllers/login_controller.rb」ファイルを開きます。このファイルをリスト1のように編集します。

リスト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 Identifierを受け付けるテキストフィールドが1つだけあります。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回の呼び出しが行われます。
図3 OpenID認証フロー。Webサイトに対して2回の呼び出しが行われます。
authenticate関数
 authenticate関数には認証ロジックのほとんどが記述されています。これは、ruby-openidライブラリのauthenticate_with_open_id関数に委任されます。この関数はログイン処理が正常に完了したかどうかに関係なく、その完了時(つまり、Webサイトへの2回目のコールバックの後)に実行されるブロックを受け付けます。このブロックには、次の3つのパラメータを指定できます。

  • resultオブジェクト
    このオブジェクトは、ログイン処理の有効性を定義します。発生する可能性があるさまざまなエラー(ユーザーがログインをキャンセルした、OpenID Providerが使用できないなど)を調査するメソッドを備えています。
  • identity_urlオブジェクト
    このオブジェクトには、ユーザーのOpenID Identifierが格納されます。
  • registrationオブジェクト
    このオブジェクトには、SREG(Simple Registration Extension for OpenID)に基づいて、ユーザーの追加情報が格納されます。
 Simple Registration ExtensionはOpenID仕様に追加された機能で、ProviderとWebサイトでユーザーに関する追加情報を交換できるようにします。例えば、ユーザーの電子メールアドレス、本名、生年月日、その他の個人データを交換できます。ユーザーは個人情報の非開示をOpenID Providerに要求してプライバシーを保護できます。前の例では: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
 これにより、OpenID Providerに対して、ログイン後にユーザーがリダイレクトされるURLは、ログイン要求の送信元ドメインと同じドメイン(信頼されたルート)に属することが保証されます。

 最後の処理が完了すると、適切に認証され識別されたユーザーのみ、アプリケーションは受け付けるようになります。また、追加情報を使用してユーザーにさらに便利な機能を提供することもできます。

 例えば、ユーザーが新しい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)資格を持つ。
このエントリーを含むはてなブックマーク この記事をクリップ!
BuzzurlにブックマークBuzzurlにブックマーク Yahoo!ブックマークに登録
最新トップニュース
データメーション
【データメーション】
OSについて気に入らないこと(9月5日)
ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」
【ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」】
「導入期〜成長期へ!一歩一歩と前進を目指す『Annoii(アノイ)』」/maka hou,Inc.(9月5日)
最新テクノロジーの意外な処方箋
【最新テクノロジーの意外な処方箋】
グリッドコンピューティング技術でETに遭遇(9月5日)
Graphic Design Forum
【Graphic Design Forum】
古い Emigre を探して (9月3日)
エンジニアの独り言
【エンジニアの独り言】
データをローカルに保存するWebアプリケーション(8月22日)
デスマーチからの脱却
【デスマーチからの脱却】
30min. iPhoneアプリリリース(8月18日)
最新ハイテク講座
最新ハイテク講座
なぜ勝った? 世界No.1シェアをつかんだ“Windows”(9月5日)
developer.com
developer.com
デザインパターンの使い方: Composite(9月5日)
最新アフィリエイト事例にみる成功の法則
最新アフィリエイト事例にみる成功の法則
コンバージョンレートを高めよう!(9月5日)
百式のネットビジネス研究
百式のネットビジネス研究
ガジェット購入時に将来の買取保証プランを提供する「TechForward」(9月5日)
週刊-サイト別アクセス状況データ
週刊-サイト別アクセス状況データ
ビデオリサーチインタラクティブ調査(月間インターネットオーディエンスデータ)(9月4日)
「IT の耳」
「IT の耳」
【書評】『検索にガンガンヒットさせる SEO の教科書』――SEO テクニックで効果的に PR する(9月4日)
検索エンジンマーケティング
検索エンジンマーケティング
果たしてモバイル SEO は必要なのか?(9月4日)
Eメールマーケティング事情
Eメールマーケティング事情
読者が迷惑メールと認識する時…(9月3日)
日本と韓国のインターネットビジネス最新動向調査
日本と韓国のインターネットビジネス最新動向調査
日本と韓国の動画サイト比較1―現状(9月3日)
SNSをビジネスに活用しよう
SNSをビジネスに活用しよう
「しまじろう」に学ぶ企業内コミュニティの活性化のポイント(9月2日)
海外のインターネットコムアメリカ韓国ドイツトルコ
Copyright 2008 Jupitermedia Corporation All Rights Reserved.http://www.internet.com/