Ktouth Brand. on Web

け〜くんこと K.Ktouth のだらだらした日常と突発的に作るプログラムや読み物とかの雑多サイト



[2009年08月24日]

Ramaze で salt を使ったユーザ認証と Aspect ヘルパーの注意点(2009/07版)

2009年08月24日 20:47更新 筆者:K.Ktouth

以前書いていたのが消えたので orz、いくつか調整した上で再度書いてみます。
Ramaze の認証にはシンプルな Auth ヘルパーと ユーザデータを使用した User ヘルパーがあります。
ユーザに関する情報がデータモデルなどになっているときは User 、単一の管理者に対する権限での認証なら Auth の方が使いやすいと思います。

で、肝心のパスワードの管理ですが、rails などの認証プラグインのように 暗号化されたパスワードと salt を組み合わせた認証を行いたい場合、以下の記事のコードが参考になると思います。

ただ、いくつかの点において補足したい点があるので、その辺を後述します。

(以下、補足のポイント)

login_first は要らない、before で

User ヘルパーを使っている際、ログインしているかどうかは #logged_in? メソッドで判断できます。
上記サイトでのログイン認証チェックは、それを直接記述するか login_first メソッドを読んで判断させています。
シンプルですが、Ramaze には Aspect ヘルパーという、そう言う処理を行うためのヘルパーが標準で機能しています。

class MemberController < Ramaze::Controller
 layout(:page) { !request.xhr? }
 map '/member'

 helper :user
 trait :user_model => (ユーザ情報に該当するデータモデル)

# 以下のメソッドはログインしていないとアクセスできず、ログインフォームにリダイレクトされる
 before(:index, :logout, :password, :edit, :list) do
  redirect rs(:login) unless logged_in?
 end

 def index
 end

 def login
 end

 def logout
 end

 def password
 end

 def edit
 end

 def list
 end
end

こう書くだけで、各メソッドで明示することなくログインチェックが可能です。

面倒だよね?

が、before メソッドには一つ面倒な作業があります。
つまり要認証メソッドが増えるたびに、before メソッドの記述を増やす必要があると言うことです。おおよそにおいて、こういった認証が必要なメソッドはコントローラ単位でまとまっているため、不要メソッドは(login メソッドのような)せいぜい一つか二つでしょう。
となれば、「login 以外のメソッド全部」という記述がしたくなるのが人情です。が、Aspect ヘルパーには before_except のようなメソッドはありません。
もちろん、かわりに before_all があるので、そちらで同様の処理をしたらいいわけですが……そこに落とし穴が。

先ほどの before メソッドを以下のように書いた場合、「終わらないリダイレクト処理」というエラーが出る事になります。

 before_all do
  redirect rs(:login) unless %w(login).include?(action.method) or logged_in?
 end

Aspect ヘルパーの注意点

Ramaze の Aspect ヘルパーは結構シンプルなもので、以下の点はきちんと把握しておかないと面倒なことになります。

  1. before_all / after_all メソッドで登録出来るブロックは、一つだけ。

    ヘルパーのソースを見ると、以下のようになっています。

    def before_all(&block)
     AOP[self][:before_all] = block
    end

    ご覧の通り、ブロックは一つしか保持できません。after_all も同様です。

  2. before / after メソッドで登録できるブロックは、対象メソッドごとに一つだけ。

    ヘルパーのソースを見ると、以下のようになっています。

    def before(*names, &block)
     names.each{|name| AOP[self][:before][name] = block }
    end

    名前単位で分かれているとはいえ、before_all と似たようなコードとなっています。after も同様です。

  3. wrap メソッドは限定した状況でしか使えない

    wrap メソッドには メソッド実行をラッピングして欲しいと考えてしまいますが……コードを見ると。

    def wrap(*names, &block)
     before(*names, &block)
     after(*names, &block)
    end

    ……という、かなり微妙な定義になっています。当然 before / after メソッドと排他利用となります。

  4. before_all / after_all は 複数回呼ばれることがある

    先ほどの「終わらないリダイレクト処理」はこれが原因です。
    通常、layout メソッドを使ったページのテンプレートの定義をしていると思います。この場合、レンダリング処理は「メソッドの実行」→「レイアウトの実行」の順に行われ、各実行ごとに before_all → before → 処理 → after → after_all の各ブロックが呼び出されます。
    レイアウト実行の際には action.method が nil になっているため、先ほどのような書き方では login メソッドを実行した後のレイアウト処理でリダイレクトが発生してしまい、永久ループ状態になっていたわけです。

これを踏まえた上で、再度修正すると……

 before_all do
  redirect rs(:login) unless action.method.nil? or %w(login).include?(action.method) or logged_in?
 end

となります。
無条件リダイレクトではなく、状況、たとえば HTML で返すか JSON でエラーデータを渡すかを切り替えたいときとかには、さらに補足がありますが……それはまた別の機会に。

……ん? これだけ把握することが出てくると、最初のリンク先サイトのように login_first のような明示的手法の方が早いかも(ぉぃ

本日のリンク元
アンテナ
その他のリンク元
検索