Ruby の CGI::Session
はてな認証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
これで準備完了。