Warden - це Rack middleware, що забезпечує механізм аутентифікації для веб-додатків на Ruby.
Warden вимагає деяких налаштувань для Sinatra. Цього можна уникнути, використовуючи плагін Sinatra::Warden. Але ми ж не шукаємо простих шляхів.
Налаштування DataMapper
Для початку створимо модель і таблицю бази даних для зберігання імені користувача і паролю:class User
include DataMapper::Resource
property :id, Serial
property :username, String, :unique => true
property :password, String
end
Налаштування Rack
Повинен бути оголошений додаток для невдач, а також, які стратегії аутентифікації використовуватимуться по замовчуванню.use Rack::Session::Cookie, :secret => "bla-bla-bla"
use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = FailureApp.new
end
class FailureApp
def call(env)
uri = env['REQUEST_URI']
puts "failure #{env['REQUEST_METHOD']} #{uri}"
end
end
Налаштування сесії
Одним з результатів використання будь-якого об'єкта, наприклад об'єкта User, є те, що ви повинні сказати Warden, як серіалізувати його у та із сесії. Це потрібно налаштувати:Warden::Manager.serialize_into_session do |user|
user.id
end
Warden::Manager.serialize_from_session do |id|
User.get(id)
end
Після успішної аутентифікації в сесії буде зберігатися ідентифікатор користувача.Стратегія аутентифікації
Стратегія у Warden містить логіку для перевірки аутентифікації запиту.Warden::Strategies.add(:password) do
def valid?
params['username'] || params['password']
end
def authenticate!
u = User.authenticate(params['username'], params['password'])
u.nil? ? fail!('Could not login in') : success!(u)
end
end
Вище ми оголосили стратегію під назвою :password, яка містить два стандартні методи #valid? і #authenticate!.Необов'язковий метод #valid? діє в якості охорони для стратегії. Якщо ви не визначите його, стратегія завжди буде працювати. В іншому випадку стратегія виконуватиметься тільки якщо #valid? поверне значення true.
Стратегія, яку ми оголосили вище передбачає, що якщо переданий хоча б один з параметрів 'username' чи 'password', то користувач намагається увійти. Якщо є тільки один з ним, то далі виклик User#authenticate закінчиться невдачею.
У методі #authenticate! відбувається аутентифікація. Він містить логіку для перевірки аутентифікації запитів.
Тут же і містяться дві стандартні дії: fail! і success!.
success! встановлює успішну аутентифікацію. Екземпляром класу User серіалізується у сесію.
fail! встановлює, що стратегію пройшла безуспішно.
Для перевірки аутентифікації будемо використовувати відкритий метод User#authenticate, який повертатиме об'єкт User, якщо передане коректне ім'я користувача і пароль:
class User
include DataMapper::Resource
# ...
def self.authenticate(username, password)
user = first(:username => username)
if user
if user.password != password
user = nil
end
end
user
end
end
Звичайно, цей метод написаний з метою демонстрації. Насправді, логіка аутентифікації може бути настільки складною, наскільки це необхідно.Після успішної аутентифікації, екземпляр класу User серіалізується (Warden::Manager.serialize_into_session) у сесію. Далі його можна отримати через змінну оточення env['warden'].user, шляхом десеріалізації (Warden::Manager.serialize_from_session) по ідентифікатору.
Маршрути Sinatra
Ось ми і дісталися до завершальної стадії нашої статті. Настав час визначити маршрути для для нашого веб-додатку.get '/' do
# ...
end
get '/login/?' do
# ...
end
post '/login/?' do
# ...
end
get '/logout/?' do
# ...
end
На головній сторінці виводитиметься привітання для користувача, якщо він автентифікований на сайті. Інакше переправлятиметься на сторінку '/login' з формою для аутентифікації.
get '/' do
redirect '/login' unless env['warden'].user
slim :index
end
get '/login/?' do
slim :login
end
Головна сторінка:
p Welcome, #{env['warden'].user.username}
a href='/logout' Log out
Форма для аутентифікації містить для текстових поля 'username' і 'password'.
form action='/login' method='post'
ul
li#username
label Username:
br
input name='username' type='text'
li#password
label Password:
br
input name='password' type='text'
input type='submit' value='Log in'
Після того як користувач введе ім'я і пароль відбувається перевірка аутентифікації. І якщо вона пройшла успішно, користувач переправляється на головну сторінку з привітанням. Ця ж сторінка містить посиланням для виходу.
post '/login/?' do
if env['warden'].authenticate
redirect '/'
else
redirect '/login'
end
end
get '/logout/?' do
env['warden'].logout
redirect '/'
end
На всяк випадок, викладу тут повний код додатку.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- encoding: utf-8 -*- | |
require 'sinatra' | |
require 'slim' | |
require 'warden' | |
require 'dm-core' | |
require 'dm-migrations' | |
DataMapper::Logger.new(STDOUT, :debug) | |
DataMapper.setup(:default, "sqlite:///#{Dir.pwd}/project.db") | |
# Model and database table to hold administrator's usernames and passwords | |
# | |
class User | |
include DataMapper::Resource | |
property :id, Serial | |
property :username, String, :unique => true | |
property :password, String | |
# Public class method than returns a user oblect if the caller supplies the correct name and password | |
# | |
def self.authenticate(username, password) | |
user = first(:username => username) | |
if user | |
if user.password != password | |
user = nil | |
end | |
end | |
user | |
end | |
end | |
# finalize them after declaring all of the models | |
DataMapper.finalize | |
# wipes out existing data | |
DataMapper.auto_upgrade! | |
# create test user | |
#User.create(:username => 'user', :password => 'qwerty') | |
### Rack Setup | |
# | |
use Rack::Session::Cookie, :secret => "bla-bla-bla" | |
use Warden::Manager do |manager| | |
manager.default_strategies :password | |
manager.failure_app = FailureApp.new | |
end | |
### | |
### Session Setup | |
# Tell Warden how to serialize the user in and out of the session. | |
# | |
Warden::Manager.serialize_into_session do |user| | |
puts '[INFO] serialize into session' | |
user.id | |
end | |
Warden::Manager.serialize_from_session do |id| | |
puts '[INFO] serialize from session' | |
User.get(id) | |
end | |
### | |
### Declare Some Strategies | |
# | |
Warden::Strategies.add(:password) do | |
def valid? | |
puts '[INFO] password strategy valid?' | |
params['username'] || params['password'] | |
end | |
def authenticate! | |
puts '[INFO] password strategy authenticate' | |
u = User.authenticate(params['username'], params['password']) | |
u.nil? ? fail!('Could not login in') : success!(u) | |
end | |
end | |
### | |
class FailureApp | |
def call(env) | |
uri = env['REQUEST_URI'] | |
puts "failure #{env['REQUEST_METHOD']} #{uri}" | |
end | |
end | |
get '/' do | |
redirect '/login' unless env['warden'].user | |
slim :index | |
end | |
get '/login/?' do | |
slim :login | |
end | |
post '/login/?' do | |
if env['warden'].authenticate | |
redirect '/' | |
else | |
redirect '/login' | |
end | |
end | |
get '/logout/?' do | |
env['warden'].logout | |
redirect '/' | |
end | |
__END__ | |
@@index | |
p Welcome, #{env['warden'].user.username} | |
a href='/logout' Log out | |
@@login | |
form action='/login' method='post' | |
ul | |
li#username | |
label Username: | |
br | |
input name='username' type='text' | |
li#password | |
label Password: | |
br | |
input name='password' type='text' | |
input type='submit' value='Log in' |
Немає коментарів:
Дописати коментар