Rackについてメモ
request.env の正体
Wardenの内部実装を読んでいると、セッションへの書き込みを行っているのは Warden::SessionSerializer#store
メソッドなのだが、そのメソッドは以下の処理をしている。
session[key_for(scope)] = specialized ? send(method_name, user) : serialize(user)
つまるところ、指定したkey名で セッションへの書き込みをしているのだが、より詳細を見ていく。
Warden::SessionSerializer#session
メソッドは以下のように定義されている
def session
env["rack.session"] || {}
end
ここでの env
は request.env
と同じなので、 request.env['rack.session']
を実行していることになる。
したがって、Warden::SessionSerializer#store
で行っているのは、
request.env['rack.session'][key_for(scope)] = specialized ? send(method_name, user) : serialize(user)
という処理であり、request.env['rack.session']
を操作するラッパーでしかないことが分かる。
そこで、request.envについて見てみる
> request.class
=> #<ActionDispatch::Request
> request.env.class
ActionDispatch::Request::Session
ActionDispatch::Request::Session
この ActionDispatch::Request::Session クラスに定義されている []
メソッドを見てみると、その内部で読んでいる id
メソッドがやっていることが、以下のメソッドの呼び出しであることが分かる
def self.find(req)
req.get_header ENV_SESSION_OPTIONS_KEY
end
ここで req.get_header
は request.get_header
と同じであり、 ENV_SESSION_OPTIONS_KEY は Rack::RACK_SESSION_OPTIONS
である。
つまり、request.get_header Rack::RACK_SESSION_OPTIONS
を呼んでいることになる。
というわけで、結局はRackで定義しているHeaderの値を取得したり書き込んだりして、セッションの実現をしているということが分かる。
Wardenのauthenticateのメソッドが返すオブジェクト
Warden::Manager.serialize_from_session
を定義している箇所を見ればわかる
どのドメインにてセッションを作成・維持するか
セッションとはつまり、状態のこと。
ユーザーエージェント(ブラウザ)ごとに、ある状態を維持するための仕組みとして、HTTPリクエスト時にCookieを送る。
Cookieの中には任意の文字列が入っており、HTTPリクエストを受けたサーバ側はその文字列を解釈して、リクエストを送ってきたユーザーエージェントが何者かを判定する(=認証)
Cookieを生成し、どのドメインに向けたリクエストの際はCookieも一緒に送るかを指定する権限を持つのは、サーバー側である。レスポンスヘッダにSetCookie ヘッダを付与することで任意のCookieをセットできる。
リクエスト時にCookieを添付するということは、そのリクエスト先のサーバーに対して「私はこういう素性のユーザーエージェントです」と自己紹介することとみなせる。
その自己紹介が真正であるとサーバー側が検証できることを、認証が通ったこととみなせる。
CookieのオプションであるDomain
属性は、どのドメインに対するリクエストの際に自身(=Cookie)を添付するかを指定するものであった。
つまり、送付先のドメインを指定することで、そのCookieによって自分自身(=ユーザーエージェント)が何者であるかを証明しようとする相手たるサーバーを限定することができる。
これは、ユーザーエージェントはCookieを用いて、どのドメインに自身のセッションを管理させるかを指定できるということになる。
Rackを使ったセッション管理
- RackはWebサーバが受け取ったHTTPリクエストをいい感じに整理し、ラップしたオブジェクトを作ってくれる
- そのオブジェクトの中には、ユーザーエージェントから送られたCookieの情報も含まれている
- このCookieの情報を解釈することで、セッションの生成・維持が可能となり、認証/認可を実現できる。
- Wardenは認証/認可のインターフェースを提供するgemだが、Rackの存在をベースとしている。直接HTTPリクエストを解釈しその情報を格納するという責務はRackに任せ、Rackが用意してくれたHTTPリクエスト/レスポンスを解釈/生成するインターフェースを用いて、認証と認可の操作をWardenは提供している。
- Railsの場合は、Rackを生で使わずActionDispatchを用いるので、ActionDispatchが提供するインターフェースを用いるよう最適化したgemである
rails-warden
というgemがあるが、このgem自身ももちろん Wardenを便利に使うためのインターフェースを提供するのが責務である
ログインについて
ActionDispatch::Request::Session にセッション情報を、Railsでは格納している
> request.env['rack.session']
=> #<ActionDispatch::Request::Session:0x00007f9ed5bd7830 ...>
WardenのSessionSerializerをみると、以下のように session
と key_for
と store
メソッドが定義されている
def session
env["rack.session"] || {}
end
def key_for(scope)
"warden.user.#{scope}.key"
end
def store(user, scope)
return unless user
method_name = "#{scope}_serialize"
specialized = respond_to?(method_name)
session[key_for(scope)] = specialized ? send(method_name, user) : serialize(user)
end
ここで定義されている store メソッドが呼ばれると、request.env['rack.session']["warden.user.#{scope}.key"]
に、Warden::Manager.serialize_into_session
のブロックで定義している値が、シリアライズされた値として入る (hash形式)
store メソッドが呼ばれるのは、Warden::Proxy#set_user
の内部。
set_user が呼ばれるのは、いわゆる current_user
メソッドを初回に呼んだとき
これが呼ばれると、デフォルトのscopeでは、 request.env['rack.session']['warden.user.user.key']
でシリアライズされた値を取得できるようになる。
ログアウトについて
Rails Wardenでは、RailsWarden::Mixins::ControllerOnlyMethods#logout
が定義されている。
そこで行っているのは、 request.env['warden'].logout
と同じ。
つまり、Warden::Proxy#logout
を呼んでいる。
ここでは結局のところ、 Warden::Mixins::Common#reset_session!
が呼ばれ、request.env['rack.session'].clear
を実行している。
request.env['rack.session'].clear
によって request.env['rack.session']
の中身がnilになるため、 request.env['rack.session']['warden.user.user.key']
にアクセスしてもシリアライズされた値が取得できず、nilが帰ってくるだけとなる。
これは、authenticate!
の戻り値がnilになることを意味するので、fail!
が呼ばれて、いわゆるなログアウト状態になる
Cookieについて
request.env['action_dispatch.cookies']
を参照
Rackでの定義はこちら
Class: Rack::Session::Cookie — Documentation for rack/rack (master)