Rails 2.0のセッション話
Rails 2.0はセッションはCookieに入れる、というのを読んで*1Cookie-sessionなんじゃらほい、と思ったのでちょっとソースを見てみました。
「ふつう」セッションに入れるようなちょっとしたデータは4K制限のあるCookieでも十分のはずだよねぇ、ということでセッションにいれる情報をMarshalしてCookieに入れちゃいましょう、というのがこの方式のポイントです。
で、Cookieに入れるっていうとユーザが自由自在にいじれるわけで、信用していいんだっけ?というのが気になったわけです。
見てみた結果はまぁ大丈夫そう。データに突っ込んだ内容とそのdigestの両方をCookieに入れて、受け付けたときはそれを検証するという手順になってるみたいです。digestを生成するときはsecretも必要になりますが、それがconfig/environment.rbで指定することになったアレですね。どこで使ってるのかと思ったらこんなところに。
参考: actionpack-2.0.1/lib/action_controller/session/cookie_store.rb
参考としてソース読んだときの要点を抜き出したつもりなんですが、なんかソースをそのまま乗っけたのと変わんない気がしてきました orz。
いちおう restore => unmarshal => generate_digest => initialize と close => marshal => generate_digest という順番に読むと雰囲気が伝わるんじゃないかと思います。
# actionpack-2.0.1/lib/action_controller/session/cookie_store.rb class CGI::Session::CookieStore def initialize(session, options = {}) ... # 59行目付近 # Keep the session and its secret on hand so we can read and write cookies. @session, @secret = session, options['secret'] # Message digest defaults to SHA1. @digest = options['digest'] || 'SHA1' ... end .... # 96行目付近 # Restore session data from the cookie. def restore @original = read_cookie @data = unmarshal(@original) || {} end .... # Write the session data cookie if it was loaded and has changed. def close if defined?(@data) && !@data.blank? updated = marshal(@data) raise CookieOverflow if updated.size > MAX write_cookie('value' => updated) unless updated == @original end end .... # 122行目付近 # Generate the HMAC keyed message digest. Uses SHA1 by default. def generate_digest(data) key = @secret.respond_to?(:call) ? @secret.call(@session) : @secret OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), key, data) end private # Marshal a session hash into safe cookie data. Include an integrity hash. def marshal(session) data = Base64.encode64(Marshal.dump(session)).chop CGI.escape "#{data}--#{generate_digest(data)}" end # Unmarshal cookie data to a hash and verify its integrity. def unmarshal(cookie) if cookie data, digest = CGI.unescape(cookie).split('--') unless digest == generate_digest(data) delete raise TamperedWithCookie end Marshal.load(Base64.decode64(data)) end end end