のらぬこの日常を描く

ノージャンルのお役立ち情報やアニメとゲームの話、ソフトウェア開発に関する話などを中心としたブログです。

Railsで認証画面を自作してみた

railsでユーザ認証画面を自作してみた。

さすがに眠いので、このトピックかなり適当になってます。。。。

起きたら清書します、たぶん。。。。


ではいきます。

まずはdbスキーマの定義。migrate後に出来上がる db/schema.rbを載せておきます。

  # db/schema.rb
  # userName に ユーザ名が、password に cryptされたpasswordが入ります。 
  create_table "authentications", :force => true do |t|
    t.integer  "userId"
    t.string   "userName",   :null => false
    t.string   "password",   :null => false
    t.integer  "disable"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

続いて authenticationのmodel 定義です。

webを見ていると、modelは単なる O/Rマッパーとして使用し、認証ロジック本体はcontrollerに埋め込んでる例が多い気がしますが

与えられたパラメータ(username, password)が正当性を判定する処理は、ユーザ操作の延長にあるものではなく、modelに問い合わせるもの*1であるため、modelに定義するのが設計的には妥当と思う。

passwordのhashにSHA512なんていうご大層なものを使ってますが、別にString.cryptで十分だったかな(パフォーマンス的にも)。。。

# app/model/authentication.rb
require 'digest/sha2'
class Authentication < ActiveRecord::Base
  @@digest = Digest::SHA512.new
  def Authentication.digest
    @@digest
  end
  
  def Authentication.crypt target
    digest.hexdigest target
  end
  
  def Authentication.authorize name, password
    target = find( :first, :conditions=> { :userName=>name, :password=>crypt(name + ":" + password), :disable=>0 }, :select=>'userId')
    if target != nil
      target['userId']
    else
      false
    end
  end
end

ユーザ登録画面は未実装なので、とりあえず仮ユーザをダミー登録しておきます

# db/seeds.rb
Authentication.create( { :userId=>1, :userName=>"admin", :password=>Authentication.crypt("admin:admin"), :disable=>0 } )

次に、controller, view の実装に移ります。

まずはページにアクセスされた際、認証済みかを判定する部分を記述します。

ttp://....:3000/<controler>/<action>/<id> が呼び出されたらユーザ認証が完了しているかを調べます。

ログイン済みであれば、指定されたページをロードします。

ログインしていない場合、ログイン画面にリダレクトします。

railsでは、コントローラのアクションメソッドが呼び出される前に動作するfilterを定義することができます。

ApplicationController内で、before_filterとしてフィルタを定義しておけば、すべてのcontroller/actionで有効なfilterを登録できます。

session[:jumpto] に、リクエストパラメータを保存しておくことで、ログイン後指定ページにリダレクトできるようにしています。

認証が完了しているかの判定には、session[:userName] にユーザ名が入っているかで判定していますが、

これではあまりにもお粗末なので、ちゃんと作るなら、ログイン時にsessionIdを割り当てて、サーバ側でもログイン中のsessionリストを管理する程度のことは必要かと。

# app/controller/application_controller.rb
class ApplicationController < ActionController::Base
  before_filter :authorize
  def authorize
    unless session[:userName]
      session[:jumpto] = request.parameters
      redirect_to :controller => 'user', :action => 'login'
    end
  end
end

で、次は ログイン・ログアウトのためのcontrollerクラスです。

skip_filter で、login アクション時は、ログイン済みchekを飛ばしています*2

# app/controller/user_controller.rb
class UserController < ApplicationController
  skip_filter :authorize, :except=>:login
  
  def login 
    session[:userName] = nil
    @user = params['userName']
    @password = params['password']
    if ( @user != nil ) && ( @password != nil )
      if Authentication.authorize(@user, @password) != false
        session[:userName] = @user
        flash.discard
        if session[:jumpto] != nil
          redirect_to session[:jumpto]
        else
          redirect_to({:controller=>'user', :action => 'loggedin'})
        end
      else
        flash[:notice] = "invalid username or password."
      end
    end
  end
  
  def loggedin
  end

  def logout
    session[:userName] = nil
  end
end

で、最後はview部分

ログイン画面は、user name、passowrdが入力されるテキストエリアを貼り付けたformを表示しています。

@user, @password には、直前に入力した user name、password が格納されます。

つまり、user name、passwordのいずれかを間違え、認証に失敗した場合に、前回入力した内容を初期値として表示します。

<% form_tag do %>
<p><%= label_tag("Username") %> <%= text_field_tag("userName", @user ) %> </p>
<p><%= label_tag("Password") %> <%= text_field_tag("password", @password ) %> </p>
<%= submit_tag "Send" %>
<% end %>

んで、次が logout画面

logout しました。再びログインするには login をクリックしてください。

みたいな画面です。

<h1>User#logout</h1>
<p>Find me in app/views/user/logout.html.erb</p>
<%= link_to 'login', {:action => 'login'} %>

最後にログイン完了画面。

とりあえず作っただけ。。。。

<p>User#loggedin</p>

*1:大げさな言い方すればビジネスロジックに当たるもの

*2:ログインしていない、もしくはログインユーザを変更死体からlogin画面を出す訳なので