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


以上