FrontPage  Index  Search  Changes  Login

RailsでWikiクローンを作る11

認証の適用

session

HTTP はもともとステートレスな(状態を持たない)プロトコルです。なので、 例えば今どんなユーザでログインしているかという情報を保持するには、一連 のセッションを識別して管理する仕組みが必要になります。セッション管理の 方法はいろいろありますが、Rails は Cookie をベースにしたセッション管理 機構を標準で装備しています。

実際に Rails でセッションの機構を使う場合は、Cookie を意識する必要は全 くありません。コントローラから参照できる session というハッシュのよう な属性があり、任意のキーで任意のオブジェクト(一部制限あり)を session に格納しておくことができます。そしてsessionの内容は複数のアクションに またがって参照することができます。

既に、似たようなものとして flash について説明していますが、flash の内 容は次のアクションに限って伝達されるのに比べ、session はもう少し永続的 です(Railsデフォルトでは、同一ブラウザでアクセスしている間ずっと保持さ れる)。

login アクション

では、ユーザ名とパスワードの入力を求める login アクションを app/controllers/admin_controller.rb に追加します。入力されたユーザ名と パスワードが正しければ、そのユーザ情報を session に保持してログイン状 態にする、という動作をします。

 def login
   if request.get?
     session[:user] = nil
     @user = User.new
   else
     user = User.new(params[:user])
     @user = User.find_by_name(user.name)
     if user.password.crypt(@user.crypted_password) == @user.crypted_password
       session[:user] = @user
       jumpto = session[:jumpto] ||
         {:controller => 'wiki', :action => 'show', :id => 'FrontPage'}
       session[:jumpto] = nil
       redirect_to(jumpto)
     else
       flash[:notice] = "ユーザ名またはパスワードが間違っています。"
     end
   end
 end

login メソッドでは、basic と同じように request method によって動作を変 えています。 GETメソッドの場合は、session に記録されている :user 情報 を消して、ログイン用フォームを表示します。 GETメソッドでない場合は POSTメソッドと思われるので、params で受けとったユーザ名・パスワードと、 DBに記録されているものと比較します。一致していたら session[:user] にユー ザ情報を記録し、これ以降ログインしている状態になります。 また、無事ロ グイン状態になったらリダイレクトするのですが、もし session[:jumpto] に 値が入っていたらそれで指定される先(後述)に、値がないなら FrontPage に リダイレクトします。

ビューのテンプレート app/views/admin/login.rhtml は以下の通りです。

<% @title = "login" %>

<%if flash[:notice] %>
<p style="margin-left: 2em; color: green"><%= flash[:notice] %></p>
<% end %>

<%= start_form_tag %>
<div class="body">
<p><label for="user_name">ユーザ名</label>
<%= text_field("user", "name") %></p>
<p><label for="user_password">パスワード</label>
<%= password_field("user", "password") %></p>
<%= submit_tag "ログイン" %>
</div>
<%= end_form_tag %>

ログイン画面(http://localhost:3000/admin/login):

admin/login

フィルタ

認証に必要な準備はひととおり整いました。次は、この認証を必要なページに 適用します。認証を適用するには Rails のフィルタ機能を使います。フィル タ機能は、各アクションの実行時に割り込んで、指定のメソッドを実行する機 能で、複数のアクションにまたがった前処理・後処理や、アクセス制御に利用 することができます。

実は、Minki では既にフィルタ機能を使っています。 準備段階の「UTF-8で運 用する」において、before_filter というアクション実行前に実行されるフィ ルタを利用して、set_charset というメソッドを実行するようにしています。

class ApplicationController < ActionController::Base
  before_filter :set_charset

  protected
  def set_charset
    @headers["Content-Type"] = "text/html; charset=utf-8" 
  end
end

フィルタはコントローラ単位で設定しますが、set_charset は、すべてのコン トローラの上位クラスである ApplicationController で before_filter とし て設定されているため、Minki アプリケーションのすべてのアクション前に set_charset が実行されることになります。これは、フィルタを前処理として 利用した例です。

では、before_filter を認証に使います。まずは、before_filter で指定する メソッド authorize を作ります。 authorize は、session を調べてユーザ情 報があるかないか、つまり現在ログイン中かどうかを調べ、ログイン中でなけ ればログイン画面にリダイレクトするメソッドです。これを必要なアクション の前に適用すれば、ログインしないとアクションを実行できなくなります。

認証は複数のコントローラのアクションにしかけますので、authorize メソッ ドは ApplicationController (app/controllers/application.rb)に定義する ことにします。

class ApplicationController < ActionController::Base
  before_filter :set_charset

  protected
  def set_charset
    @headers["Content-Type"] = "text/html; charset=utf-8" 
  end

  def authorize
    unless session[:user] and
        (session[:user].name == 'admin' or params[:controller] != 'admin')
      flash[:notice] = "ログインしてください"
      session[:jumpto] = request.parameters
      redirect_to :controller => 'admin', :action => 'login'
    end
  end
end

ApplicationController の before_filter で authorize メソッドを指定して はいけません。もしやってしまうと、すべてのアクションに認証がかかってし まします。

authorize メソッドでは、session[:user] を見てログイン中かどうかを調べ、 以下の条件を満たしているかを判断します。

  • adminコントローラならば、ユーザ名が'admin'でログインしていること
  • それ以外のコントローラならば、任意のユーザでログインしていること

この条件を満たしていれば、何もせずにスルーします。つまり、本来実行する アクションが普通に実行されます。条件を満たしていなければ、adminコント ローラのloginアクションにリダイレクトします。ログインしない限りloginア クションにリダイレクトされ続けることになります。

リダイレクトする直前に、session[:jumpto] = request.parametersとして、 sessionにリクエストのパラメータを保存しています。こうすることにより、 loginアクションで認証を通過したら、session[:jumpto] にリダイレクトする ことにより元のアクションに戻ってくることができるようになります。

そして、authorize メソッドを各コントローラの before_filter で設定しま す。まずは admin コントローラ app/controllers/admin_controller.rb:

class AdminController < ApplicationController
  before_filter :authorize, :except => :login
  #(以下略)

before_filter として authorize メソッドを登録しています。その後ろはオ プションで、:except でフィルタを適用しないアクション名を指定しています。 login アクションに認証をかけるわけにはいかないので login はフィルタ適 用対象外にし、それ以外のすべての(adminコントローラの)アクションに認証 をかけます。 :except と、次に出てくる :only には、アクション名単体ある いはアクション名の配列のどちらも指定できます。

次に wiki コントローラ app/controllers/wiki_controller.rb:

class WikiController < ApplicationController
  before_filter :authorize, :only => [:new, :create, :edit, :update]
  #(以下略)

こちらは admin コントローラとは逆に :only オプションを指定して、new, create, edit, update の各アクションのみに認証をかけています。この行を 書かなければ、Wikiページは誰でも作成・変更が可能のままになります。

logout

logout アクションは簡単です。 session[:user] をクリアして、FrontPage へリダイレクトするだけです。ビューはありません。

  def logout
    session[:user] = nil
    redirect_to :controller => 'wiki', :action => 'show', :id => 'FrontPage'
  end

そして、app/views/layouts/wiki.rhtml に以下のコードを追加して、ログイ ン状態ならば各ページの上部に表示されるadminmenuに「ログアウト」のリン クを表示するようにしておきます。

 <% if session[:user] %>
  <span class="adminmenu">
  <%= link_to 'ログアウト', :controller => 'admin', :action => 'logout' %>
  </span>
 <% end %>

このコードは、<div class="adminmenu">の中ならばどこに入れても良いです が、最後に入れたとすると、ログイン中のadminmenuは以下のようになります。

adminmenu

Last modified:2005/11/27 20:39:03
Keyword(s):
References:[RailsでWikiクローンを作る]