« RailsでWikiクローンを作る | メイン | Wiki新設 »
2005年09月15日
RailsでWikiクローンを作る 13: 認証の適用
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):

フィルタ
認証に必要な準備はひととおり整いました。
次は、この認証を必要なページに適用します。
認証を適用するには 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'でログインしていること
- それ以外のコントローラならば、任意のユーザでログインしていること
リダイレクトする直前に、
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は以下のようになります。

投稿者 tam : 2005年09月15日 09:42
トラックバック
このエントリーのトラックバックURL:
http://tam.qmix.org/mt3/mt-tb.cgi/20
このリストは、次のエントリーを参照しています: RailsでWikiクローンを作る 13: 認証の適用:
» Great post! I'm looking forward for more. from
[続きを読む]
トラックバック時刻: 2005年12月08日 01:54