Ruby
Rails
rack

Rails で (Rack の) セッション情報を Cookie に保存する仕組み

More than 5 years have passed since last update.

Rails では特に (config/initializers/session_store.rb などで) 設定しない場合、セッションの情報をクライアントの Cookie に直接、(事実上) 平文で保存する。

実際に見てみよう。

$ rails new todo

$ cd todo
$ rails g scaffold tasks name
$ rake db:migrate
$ rails s

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
protect_from_forgery
before_filter do
# アクセスした日時を session に保存
session[:last_accessed_at] = Time.now
end
end

$ curl localhost:3000/tasks -i -s | grep Set-Cookie

Set-Cookie: _todo_session=BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJTE0YTYyZmNlYmVhODVmNjU5NGZmNjlhN2M0MTM1ZjQ0BjsAVEkiFWxhc3RfYWNjZXNzZWRfYXQGOwBGSXU6CVRpbWUNREscgLb0JXIHOgtAX3pvbmVJIghKU1QGOwBUOgtvZmZzZXRpApB%2BSSIQX2NzcmZfdG9rZW4GOwBGSSIxU1llVWZyclBzVU9oUG0rclRMQ1hkUzNpYUU1RlpRWGI0QUU2alJ5UDJEYz0GOwBG--b7446faf0dcddf99f358ce40525f7eade939d1be; path=/; HttpOnly

require "uri"

require "base64"
require "pp"

# 上で Set-Cookie ヘッダに書かれてる文字列
session_in_cookie = "BAh7CEkiD3Nlc3Npb25faWQGOgZFRkkiJTYxMzE4MDQ5ZDA2ZjM1NjlhNGJmM2I2ZDMwYjJmMDA4BjsAVEkiFWxhc3RfYWNjZXNzZWRfYXQGOwBGSXU6CVRpbWUNREscgJmmKHsHOgtAX3pvbmVJIghKU1QGOwBUOgtvZmZzZXRpApB%2BSSIQX2NzcmZfdG9rZW4GOwBGSSIxOHcrUGlocmJIRDhpR01QZVRVMkVzZ0xsOVVJQnk4eEU0bzlpa3RoYVNvbz0GOwBG--394e1c8682803d5109d2bfbffe4254430e41098e"

# URI デコード、-- の前を取り出し
session_base64, digest = URI.decode(session_in_cookie).split("--")

# Base64 デコードして Marshal.load でオブジェクト復元
pp Marshal.load(Base64.decode64(session_base64))
# {"session_id"=>"61318049d06f3569a4bf3b6d30b2f008",
# "last_accessed_at"=>2013-03-26 13:30:50 +0900,
# "_csrf_token"=>"8w+PihrbHD8iGMPeTU2EsgLl9UIBy8xE4o9ikthaSoo="}

このように Base64 のデコードと Marshal.load で簡単に復元できてしまう。これだといっけんユーザがセッションの内容書き換え放題にみえるが、実際にはそんなことはない。

上のコードでさらっと split しているが、Cookie に格納される情報は Marshal.dump -> Base64 エンコードした文字列のあと -- に続いて、ハッシュ値が書かれている。これは -- より前の文字列と秘密鍵を使って OpenSSL::HMAC.hexdigest で作成したもので、Cookie 生成時に付与され、受け取ったときに一致するか検証される。

# rack-1.4.1/lib/session/cookie.rb より

module Rack
module Session
class Cookie
def generate_hmac(data, secret)
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
end
end
end
end

このため、セッションの中身を書き換えたところで、秘密鍵を知らない限り、適切なハッシュ値を付与することができず、改ざんを防げるわけだ。

この秘密鍵は Rails アプリケーション作成時にランダムに生成されるもので、下記のように設定されている。長い。

# config/initializers/secret_token.rb

Todo::Application.config.secret_token = 'aeb84aaf07fb5b4f8ebbfcbd8e524b64e595a436fffe0b64a54d4367c99c02bf9bef939755af13a49117fa3e526502fc297948f936e33984b9503afbeb1b359c'

素人目には充分なように安全にみえる (詳しい方のつっこみ歓迎)。

なお、最初に見たように session の内容は、リクエストした人本人には丸見えなので、秘密にしておくべき情報をセットしないよう注意。

また、CookieStore 以外の方式、たとえば DB にセッションの情報を記録する場合では、Cookie には session_id のみを持たせる一般的な実装になる。