session data integrity
environment.rb が変わっています。こんな感じで、config.action_controller.session = {:session_key => .. , :secret => .. } をつけないと怒られます。
characters = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a secret = Array.new(48){characters[rand(characters.size)]}.join config.action_controller.session = { :session_key => "_webjourney_session", :secret => secret }
なんで?ともって、Rails 2.0 の セッション周りのコードを読む。
Rails 2.0では、セッションのデフォルトストアがCookieになりました、という噂からactionpack/lib/action_controller/session/cookie_store.rb を参照します。
# 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
こんな感じで、Cookieに対してセッションデータを読み書きします。marshalして渡しておいて、unmarshalで元に戻す、と。
# Marshal a session hash into safe cookie data. Include an integrity hash. def marshal(session) data = ActiveSupport::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(ActiveSupport::Base64.decode64(data)) end end
つまり、Cookie に書き出すときに、(本物のデータ)--(digestデータ)で書き出すルールにしておいて、Cookieを受け取ったときは、digestデータが一致することで信用するようです。さらに、generate_digestメソッド。
# 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
ここで、秘密鍵 @secret がでてきているわけですが、これをenvorinment.rbで指定しろ、ということのようです。digestが異なると例外が発生するようなので、アプリケーションで固定にした方がよさそうですね。最初にあげた例では、起動時に乱数でとってきてたので、これではまずい(ユーザーがアクセス中に再起動すると例外がかならず発生する)。
というわけで、
# ruby -e "c=('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a;puts Array.new(48){c[rand(c.size)]}.join"
で48文字生成しておいて、
config.action_controller.session = { :session_key => "_webjourney_session", :secret => "2XTUIOx7hbFeCYnxxKMyMpEKfnHgq78PlGhzYS9hsyD0ByCS" }
で書くと。
それにしても、これで、セッションはサーバー側で保持するから安全、という眉唾ものの話が減りそうで一安心。たまに、
session[:current_password] = "hogefuga"
とかいうコードを見かけるものだから。
ちなみにWebJourneyではsessionを使っている箇所は1カ所、認証済ユーザーIDを保持するため、でした。JavaScriptでもっていてもいいんだけれど、レベルの話です。