Ruby の CGI::Session
2006-06-13
はてな認証APIで tDiary にログイン (2) で書いた「ログイン前からセッションを発行しているので、Session Fixation の危険性がある」という話に関連して、 CGI::Session の挙動を調べてみた。 やりたいことは、以下の2つ。
- セッション Cookie がある時はそれを取得し、無いときはセッションを「作らない」(ログイン状態の判定に使う。ログインしていない人には Cookie を発行しない。)
- セッション Cookie の有無に関わらず、新しいセッション Cookie を発行する(ログイン時に使う)
ログイン前から全員にセッション Cookie を発行すればいいじゃんって思うかもしれないけど、そうすると Session Fixation の危険がある。 少なくとも認証した時点で新しいセッション Cookie を発行したほうがいいらしい。 (これは、 Ruby カンファレンスでも前田さん(だったかな?)が少し言及していた)
ri CGI::Session で調べてみたところ、‘new_session’ というオプションを使えばいいみたい。 以下が ri でのサンプル。
# We make sure to delete an old session if one exists,
# not just to free resources, but to prevent the session
# from being maliciously hijacked later on.
begin
session = CGI::Session.new(cgi, 'new_session' => false)
session.delete
rescue ArgumentError # if no old session
end
session = CGI::Session.new(cgi, 'new_session' => true)
session.close
ってことで、以下のように書けばよさそう。
ログイン状態のチェック
ログイン済みの人はセッション Cookie からユーザIDを取得する。 それ以外の人にはセッション Cookie を発行しない。
begin
session = CGI::Session.new(cgi, 'new_session' => false)
rescue ArgumentError # if no old session
end
user = session['user_id']
ログイン時のセッション発行
セッション Cookie をすでに発行しているかどうかに関わらず、新しいセッション Cookie を発行する。 (古いセッション Cookie があれば削除する)
begin
session = CGI::Session.new(cgi, 'new_session' => false)
session.delete
rescue ArgumentError # if no old session
end
session = CGI::Session.new(cgi, 'new_session' => true)
session.close
session['user_id'] = user
所感
む…微妙に面倒。 CRUD の原則に従って、以下のようにはできないのかな。
CGI::Session.read # Read: セッションが無ければ nil を返す
CGI::Session.create # Create: セッションの有無に関わらず新しいセッションを発行
現在の CGI::Session.new と同じ動き(あれば取得、無ければ新規作成)は、以下のようにすれば書けるわけだし。
session = CGI::Session.read(cgi)
session = CGI::Session.create(cgi) unless session
一行でこう書いてもいいか。
session = CGI::Session.read(cgi) || CGI::Session.create(cgi)
書いてみた
require 'cgi'
require 'cgi/session'
class CGI
class Session
def self.create(request, options = {})
session = self.read(request, options)
session.delete if session
options['new_session'] = true
CGI::Session.new(request, options)
end
def self.read(request, options = {})
options['new_session'] = false
begin
CGI::Session.new(request, options)
rescue ArgumentError # if no old session
end
end
end
end
これで準備完了。