RackとWardenについて
Rackについてはこちらをみた。
じぶんの解釈
Rackとはwebサーバーを作る際のお作法(インターフェース)。
インターフェースに従ってWebサーバーを作成することで、サーバーを交換可能。 ミドルウェアを共通化できる。 という感じだろうか
Warden
General Rack Authentication Framework
Rackの上で構築される認証のフレームワーク。 有名なdeviseはwardenのラッパーになる。
Rackミドルウェアとして作られている。
session情報は env['rack.session']
Wardenは env[warden]
にオブジェクトを入れる。
このオブジェクトを用いて、認証を行うことができる。
env['warden'].authenticated? # Ask the question if a request has been previously authenticated env['warden'].authenticated?(:foo) # Ask the question if a request is authenticated for the :foo scope env['warden'].authenticate(:password) # Try to authenticate via the :password strategy. If it fails proceed anyway. env['warden'].authenticate!(:password) # Ensure authentication via the password strategy. If it fails, bail.
認証が成功されると、 user
オブジェクトへアクセスが出来る。
これは nil
以外なら何でもよい。
env['warden'].authenticate(:password)
この記述ではパスワードで認証を行っている。
この、どのように認証を行うか。を strategy
と呼んでいて、これを拡張して独自で認証機構を実装することができる。
Rackアプリからwadenを利用していく
ためしにやっていく
Rackアプリ
config.ru
というファイル名で以下を作成
require 'rack' app = Proc.new do |env| ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']] end run app
rackup
コマンドで config.ru
を読み込んでサーバーを起動する。
vagrant-ubuntu-trusty-64% rackup Puma starting in single mode... * Version 3.8.2 (ruby 2.3.0-p0), codename: Sassy Salamander * Min threads: 0, max threads: 16 * Environment: development * Listening on tcp://localhost:9292 Use Ctrl-C to stop
vagrant-ubuntu-trusty-64% curl http://localhost:9292/ A barebones rack app.%
curlでリクエストを投げると、レスポンスが確認できる。
wardenの追加
require 'rack' require 'warden' app = Proc.new do |env| ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']] end failure_app = Proc.new do |env| ['401', {'Content-Type' => 'text/html'}, ['fail.']] end use Rack::Session::Cookie, :secret => "replace this with some secret key" use Warden::Manager do |manager| manager.default_strategies :password, :basic manager.failure_app = failure_app end run app
wardenを使う際には、デフォルトのstrategyと認証が失敗した場合に呼ばれる、rack endpoint を指定する。
sessionシリアライズロジックと認証するユーザー
config.ru の run.app の前あたりに以下を入れる
Warden::Manager.serialize_into_session do |user| user.id end Warden::Manager.serialize_from_session do |id| User.get(id) end
Userクラスの定義もその下らへんに書く。
class User attr_accessor :id, :password USER_MAPPING = { a: 'hogehoge', b: 'mogemoge', } def initialize(id, pass) self.id = id self.passworkd = pass end def self.get(id) new(id, USER_MAPPING[id.to_sym]) end end
これで認証のもとなるUserの準備ができたので、認証ロジックを作りたいとおもう。
認証をしてみる
strategyは複数設定が可能。 その中の1つでも成功するか、すべての戦略を通るか、戦略がfailするまで呼ばれる。
strategy
は Warden::Strategies::Base
を継承して作られる。
実装が必要なのは、 valid?
と authenticate!
の2つ。
valid?
valid?
メソッドは strategy
の実行条件(ガード)。
宣言シない場合、strategy
は常に実行される。
上記の例では、 username, passwordのどちらか1つでもあれば戦略を実行します。
authenticate!
認証ロジック。 利用可能なメソッドがいくつかある。
- request
- session
- params
env
halt!
- strategyの実行を止める。後続のstrategyは実行されない
- pass
- strategyを実行しない
- success!
- 認証に成功。userオブジェクトをsessionに格納し、ログインする。halt!する
- fail!
- 認証失敗。halt!
- redirect!
- 別のURLへリダイレクトする。halt!
custom!
- わからん
headers
- headerを設定する
- errors
- errorオブジェクトへのアクセス
では、実際に strategy
を書いてみる。
こんな感じのコードをまた run.app の前あたりにいれる。
Warden::Strategies.add(:password) do def valid? params['id'] || params['password'] end def authenticate! if User.authenticate(params['id'], params['password']) success! User.get(params['id']) else fail!('failll') end end end
User.authenticateを実装する
def self.authenticate(id, pass) return false unless password = USER_MAPPING[id.to_sym] !!(password == pass) end
Rack endpointを修正する
app = Proc.new do |env| env['warden'].authenticate! ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']] end
やってみる
vagrant-ubuntu-trusty-64% curl "http://localhost:9292/?id=a&password=hogehoge" A barebones rack app.% vagrant-ubuntu-trusty-64% curl "http://localhost:9292/?id=b&password=hogehoge" fail.%
id=a, password=hogehogeは認証が成功し、id=b, password=hogehogeはfailしている。
scope
複数のユーザー(タイプ?)をログイン可能にする。
defautユーザーとAdminユーザーで認証ロジックを変えたい場合など、 スコープ(defalt, admin)でそれぞれ定義が可能。
callbacks
- after_set_user
- after_authentication
- after_fetch
- before_failure
- after_failed_fetch
- before_logout
- on_request
これらのタイミングにcallbackを仕込める。
Warden::Manager.after_set_user do |user, auth, opts| unless user.active? auth.logout throw(:warden, :message => "User not active") end end
set_userは認証に成功し、env[‘warden’].user にユーザーオブジェクトを入れる時のこと。 そのあとに上記のcallbackが呼ばれる。
まとめ
これだけ見ると非常にシンプル。 deviseを理解するために、wardenを見た。 次はdeviseを理解していくぞ。