2011年8月29日月曜日

Rails3を初歩から学ぶ #12 セッション管理(実装編)

この一連のエントリは
Ruby on Rails 3 Tutorial: Learn Rails by Example (Addison-Wesley Professional Ruby Series)
で参考に学んだことを凝縮してお送りしています。

今回からセッション管理を実装していきます。
Userモデルにはnameとencrypted_passwordというフィールドを用意していますが、今までDBにはnameしか保存していません。
新規登録時にはpasswordを入力してもらっていますが、これは暗号化前の値であってDBに保存していません。なので、まずは新規登録時にpasswordを暗号化してencrypted_passwordに保存するようにしましょう。
app/model/user.rbを編集します。
require 'digest'
SHAハッシュを使いたいのでdigestをrequireします。
そしてクラス宣言後のvalidatesなどが並んでいる箇所に
before_save :encrypt_password
とフィルタを追加します。
before_saveはsaveする前に呼び出されるフィルタです。ここではencrypt_passwordというプライベートメソッドを呼び出しています。ここで暗号化してから保存するようになります。ではencrypt_password(とその周辺)を実装します。

private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password= encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
まずencrypt_passwordではnew_record?メソッドを使ってDBに保存済みであるかどうかを確認します。新規登録時はUserモデルのインスタンスを生成してsaveメソッドを呼ぶ前にここに到達するのでnew_record?はtrueを返します。
new_record?がtrueを返したときはmake_saltメソッドでsaltを生成します。
以前説明したようにパスワードのハッシュを算出する際にはsaltと呼ばれるユーザー毎に異なる値を生成して付加しています。
ここではパスワードと現在時刻を組み合わせたものにSHAハッシュをかけてsaltを生成しています。
saltが用意できたらencryptメソッドでユーザーから渡されたパスワードを暗号化します。
encryptでは先ほど生成したsaltとユーザーから渡されたパスワードを組み合わせてSHAハッシュをかけています。

ここまででもう一度今の段階でrspecを実施しておきましょう。エラーの数が増えていなければ今回の修正が特に影響していないことが分かります(サインイン処理がまだ未実装なのでエラーは出ています)。
ではサインイン処理をごそっと実装しましょう。
まずはapp/mode/user.rbに以下のメソッドを追加します(publicなので先ほど編集したprivateよりも上の位置に追加するかpublicと明示して追加します)。

def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(name, submitted_password)
user = find_by_name(name)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
has_password?メソッドでは渡されたパスワード(ユーザーが入力したもの)を暗号化して、それがインスタンス変数(DBに保存されている)パスワードと一致することを確認しています。
これは後続のクラスメソッド「authenticate」から呼ばれます。authenticateメソッドではユーザーが入力したユーザー名とパスワードを受け取りユーザー名をキーにUserインスタンスを取得します。そして取得したUserインスタンスのパスワードとユーザー入力パスワードが一致するかをhas_password?メソッドを通じて検証しています。検証失敗時はnilを返します。

次にコントローラのcreateアクションを実装します。
app/controller/sessions_controller.rb のcreateアクションを以下のように実装します。

def create
user = User.authenticate(params[:session][:name],
params[:session][:password])
if user.nil?
flash.now[:error] = "サインイン出来ませんでした"
@title = "サインイン"
render 'new'
else
redirect_to user
end
end
サインインページからのパラメータがparams[:session]に格納されているので、ユーザー名とパスワードを取り出して先ほど実装したUser.authenticateに渡して認証します。
失敗するとnilが返ってくるのでflash.nowにエラーの旨を格納してサインインページを再描画しています。
認証成功時はユーザーごとのページ(users/:id)にリダイレクトします。

この段階でテストを実行するとNGが1件のみとなっているハズです。
このNGはcurrent_userが未定義であることに起因しています。
current_userメソッドの役割は今現在のUserインスタンスを取得して、サインイン状態をチェックするための窓口となるものです。
しかし今回の実装ではサインインページからcreateアクション実行時の一瞬だけ認証を実行してどこにも認証済みであるかどうかの情報を保持していません。
というわけでCookieに認証情報を書き込んでやる必要があるのですが、これは次回に。

0 件のコメント:

コメントを投稿