あすたぴのブログ

astap(あすたぴ)のブログ

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 と呼んでいて、これを拡張して独自で認証機構を実装することができる。

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するまで呼ばれる。

strategyWarden::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を理解していくぞ。