2011年11月16日水曜日
Railsを使ったアプリケーション開発 (その4 応用編 認証機能の追加)
Railsを使ったアプリケーション開発 (その1 事前準備編)
Railsを使ったアプリケーション開発 (その2 基礎編)
Railsを使ったアプリケーション開発 (その3 応用編)
3回に渡ってRailsを使ってきた。4回目は、認証機能をフィルタを利用して実装させる。
一からログインユーザを管理するテーブルを作るのは手間なので、その3にて作成済みのusersテーブル(userモデル)を利用する。
◆ フィルタを利用した認証機能
アクションの前に処理を実行させるbefore_filterを利用する。ApplicationControllerはすべてのコントローラの基底クラスになっているのでどのアクションにも事前に適用されることとなる。
# vi ./app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :check_logined
private
def check_logined
if session[:user] then
begin
@user = User.find(session[:user])
rescue ActiveRecord::RecordNotFound
reset_session
end
end
unless @user
flash[:referer] = request.fullpath
redirect_to :controller => 'login', :action => 'index'
end
end
end
protect_from_forgeryメソッドというのが気になったかもしれない。目的はCSRF対策であり、以下で呼び出される。
# cat app/views/layouts/application.html.erb
<%= csrf_meta_tags %>
Railsでは自動でCSRF対策をしてくれるので3点だけ気に留めておけばよい。
● HTTP GETを使ってデータ操作は行わない
● データ操作のリクエストはビューヘルパー(form_for、form_tag、link_to)を利用する
● レイアウト変更時はcsrf_meta_tagsメソッドを呼び出す
さてこれで前回作った検索サイトにでもアクセスしてみたとする。
※実際にはまだアクセスはできない
http://x.x.x.x/store/search
初回のアクセスでは当然unless文をたどることになるので、
ログインページへとリダイレクトされるはずだ。
http://x.x.x.x/login/index
認証が通った場合は、リクエストしたページへ行く必要があるため、フラッシュ(flash)を使ってそのページを覚えさせておく。フラッシュとはいわゆるセッションなのだが、次のリクエスト先で自動で削除されるためセッションの管理の手間が省けるメリットがある。
テンプレート変数を使うことはできない。テンプレート変数は現在のコントローラでのアクションとビューのテンプレート間でしか共有できないからだ。renderと異なり、redirect_toはリダイレクトによる処理の委譲を意味するものである。クライアントにリダイレクト先URLを返却し、クライアントは再度そのURLにリクエストをあげるという動きを考えれば理解が得られるだろう。よってこのようなページまたぎには、一時的な変数としてフラッシュを使うのである。
ではこのlogin/indexページも作成しよう。
いつもの通りコントローラとビューを作っていけばいい。
ここでは先にビューを見た方が分かりやすいだろうか。
# vi app/views/login/index.html.erb
<p style="color: Red"><%= @error %></p>
<%= form_tag :action => 'auth' do %>
<div class="field">
<label>USER NAME:<br />
<%= text_field_tag(:name, '', { :size => 20 }) %></label>
</div>
<div class="field">
<label>PASSWORD:<br />
<%= password_field_tag(:password, '', { :size => 20 }) %></label>
</div>
<%= hidden_field_tag :referer, flash[:referer] %>
<%= submit_tag 'login' %>
<% end %>
indexページのコントローラは特に今回のサンプルでは不要であろう。formタグで指定したauthページはコントローラから別URLへリダイレクトさせるためビューは不要である。
また、このauthページは当然認証は不要であるためskip_before_filterを利用している。
# vi app/controllers/login_controller.rb
class LoginController < ApplicationController
skip_before_filter :check_logined
def index
特に今回のサンプルでは不要
end
def auth
user = User.authenticate(params[:name], params[:password])
if user then
session[:user] = user.id
redirect_to params[:referer]
else
flash.now[:referer] = params[:referer]
@error = 'username / password error'
render 'index'
end
end
def logout
reset_session
redirect_to '/'
end
end
submitフォームからのPOST情報はparamsメソッドで受け取れる。paramsメソッドではURLの末尾に付与される情報や、ルートで定義されたパラメータ(例 /books/1 の1など)も取得できる。
ユーザ情報を引けなかった場合はelse文をたどるわけだが、そこでflash.nowを利用している。テンプレート変数(@~)を使わないのはなぜだろうか。それは"./app/views/login/index.html.erb"内ではhiddenタグ内にflashの結果を埋め込んでいるためである。flash.nowにしているのは、次のリクエストまでフラッシュを残しておく必要がないからだ。
ログアウト処理させるならlogoutメソッドを呼べばよい。
最後にUserモデルのメソッドをモデル内で定義しておく。
# vi app/models/user.rb
class User < ActiveRecord::Base
def self.authenticate(name, password)
where(:name => name,
:password => password).first
#:password => Digest::SHA1.hexdigest(password)).first
end
end
以上