railsのActionControllerでBasic認証

rails自体で簡単にBasic認証を使いたいときのため、Filterで実装してみた。
http://blogs.23.nu/c0re/stories/7409/
の改造版。

class BasicAuthFilter
 def initialize(*options_tmp)
   options = options_tmp.pop
   @username = options[:username]
   @passwd = options[:passwd]
   @realm = options[:realm] || 'Rails Basic Auth'
   @error_msg = options[:error_msg] || 'auth error'
 end

 def before(controller)
   username, passwd = get_auth_data(controller.request)
   headers = controller.headers
   if username != @username || passwd != @passwd
     headers["Status"] = "Unauthorized"
     headers["WWW-Authenticate"] = "Basic realm=\"#{@realm}\""
     controller.render :text => @error_msg, :layout => false, :status =>'401'
   end
 end

 # before afterの定義が必要みたいなので。
 def after(controller)
 end

 private
 def get_auth_data(request)
   username, passwd = '', ''
   if request.env.has_key? 'X-HTTP_AUTHORIZATION'
     # X-HTTP_AUTHORIZATIONがヘッダにあったら
     authdata = request.env['X-HTTP_AUTHORIZATION'].to_s.split
   elsif request.env.has_key? 'HTTP_AUTHORIZATION'
     # HTTP_AUTHORIZATIONがヘッダにあったら
     authdata = request.env['HTTP_AUTHORIZATION'].to_s.split
   end

   # Basic認証なヘッダなら
   if authdata and authdata[0] == 'Basic'
     username, passwd = Base64.decode64(authdata[1]).split(':')[0..1]
   end
   [username, passwd]
 end
end

でフィルターを作る。んでコントローラーのaround_filterに登録

around_filter BasicAuthFilter.new(:username => 'rails',:passwd =>'foo')

usernameとpasswdは必須。他にrealm(ダイアログ表示メッセージ)と@error_msg(認証失敗時の表示メッセージ)をnewの引数で渡せる。

アクションfooとそれ以外で違うユーザ名、パスワードを使いたい場合は

around_filter BasicAuthFilter.new(:username => 'rails',:passwd =>'foo_pass'), :only => 'foo'
around_filter BasicAuthFilter.new(:username => 'rails',:passwd =>'bar_pass'), :except => 'foo'

のようにonly、exceptで定義すればOK。これは標準のFilterの機能だね。ActionControllerのFilterってジョインポイントはbefore,afterしか今のところ定義できないけど、アスペクト指向だねぇ。Rubyのシンタックスをうまく利用して

 class FooController < ApplicationController
  before_filter :set_charset
 end

のように簡単にアドバイスの定義ができ、処理を織り込んでいける仕組みがすげぇ。