Gem Warden
Bài đăng này đã không được cập nhật trong 8 năm
1.Rack middleware
Trong rails nói đến authenticate mọi người thường nghĩ ngay đến "Gem devise" một công cụ đắc lực vô cùng tuyệt vời cho chức năng này. Nhưng hôm nay, tôi sẽ giới thiệu đến các bạn "Gem warden", một dependency của devise. Để hiểu về warden, chúng ta sẽ bắt đầu với Rack middleware.
- Rack cung cấp một giao diện tối thiểu, mô-đun, và khả năng thích nghi để phát triển các ứng dụng web trong Ruby.
- Ngày nay, ruby on rails, sinatra và một số ngôn ngữ khác đều dùng rack làm mặc định để nói chuyện với servers.
-Middleware:
- Nằm giữa server và framework. Rack có thể tùy chỉnh để ứng dụng của bạn sử dụng middleware. Ví dụ:
- Rack::URLMap : để định hướng cho nhiều ứng dụng hoạt động trong cùng một quá trình.
- Rack::ShowException : để bắt các ngoại lệ...
-Để hiển thị ra các middleware trong rails, bạn gõ câu lệnh:
rake middeware
use Rack::Sendfile
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001f0dab0>
use Rack::Runtime
use Rack::MethodOverride
......
-Để thêm mới một middleware trong rails, bạn gõ lệnh
config.middleware.use(new_middleware)
config.middleware.insert_after(existing_middleware, new_middleware)
config.middleware.insert_before(existing_middleware, new_middleware)
-Để đổi vị trí giữa 2 middleware
config.middleware.swap(existing_middleware, new_middleware)
-Xóa middleware:
config.middleware.delete existing_middleware
2.Warden
a. Giới thiệu
Warden là một rack based middleware, nó cung cấp một cơ chế cho việc authenticate trên các ứng dụng web ruby. Warden rất linh hoạt, nó cung cấp một cơ chế cho phép authentication trong bất kỳ 1 ứng dụng rack based nào.
b. Tại sao lại dùng warden
Hiện nay việc tạo nhiều ứng dụng cùng chạy trên một process đang thu hút rất nhiều người. Với mỗi ứng dụng sẽ yêu cầu một cơ chế authentication, nhưng với warden thì vấn đề này sẽ được giải quyết.
c. Một số đặc điểm về warden
Warden dùng strategies để authenticate nhưng nó lại không hề tạo ra cho bạn bất kì một strategies nào mà thay vào đó bạn phải tự tạo và tự code. Warden cũng không cung cấp cho chúng ta bất kì 1 view, 1 controller hay một helper nào cả.
Dependencies: Rack
*Strategies
Warden sử dụng khái niệm strategies để xác định nếu có request authenticate. Kết quả trả về sẽ là một trong 3 trạng thái sau:
- One succeeds
- No strategies are found relevant
- A strategy fails
Strategies là nơi để bạn đặt code logic cho một authenciate request.
Là lớp kế thừa từ Warden::Strategies::Base
*Callbacks
Warden cung cấp một số hàm callbacks sau:
- after_set_user
- after_authentication
- after_fetch
- before_failure
- after_failed_fetch
- before_logout
- on_request
*Failures
Khi một failure sảy ra trong qua trình xác thực authenticate, một throw[:warden] sẽ được ném ra.
[123, 132] in /home/mr/.rvm/gems/ruby-2.3.0/gems/warden-1.2.6/lib/warden/proxy.rb
123: # env['warden'].authenticate!(:password, :scope => :publisher) # throws if it cannot authenticate
124: #
125: # :api: public
126: def authenticate!(*args)
127: user, opts = _perform_authentication(*args)
=> 128: throw(:warden, opts) unless user
129: user
130: end
131:
132: # Check to see if there is an authenticated user for the given scope.
(byebug)
*Một số features khác
- Authenticate session data
- Scopes
*Một chút so sánh về devise và warden
- Dependencies: Warden: Rack, Devise: Warden
- Strategies mặc định: Warden: 0, Devise: 4
- Failure_app mặc định: Warden: không, Devise: có
- Views, controllers, helpers mặc định: Warden: không, Devise: có
- Độ linh hoạt khi có sự thay đổi: Warden: Xử lý đơn giản, Devise: Khá phức tạp.
3. Bắt đầu quá trình thực thi
Để bắt đầu bạn add gem warden vào gemfile và bundle để cài đặt.
gem "warden"
Bạn cần một model User để lưu thông tin đăng nhập
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.string :password
t.string :authentication_token
t.timestamps null: false
end
end
end
Add warden vào Rack middleware
config/application.rb
config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
end
Vì thứ tự các rake middleware rất quan trọng, nó ảnh hưởng đến các tính năng được thực hiện ở các rack tiếp theo. Thường thì ta sẽ add warden sau ActionDispatch::Session::CookieStore, nhưng để sử dụng được Flash, chúng ta nên add sau ActionDispatch::Flash
Sau đó tạo 1 file strategy và code logic tại đây
lib/strategies/password_strategy.rb
class PasswordStrategy < ::Warden::Strategies::Base
def valid?
return false if request.get?
user_data = params.fetch("session", {})
(user_data["email"]&&user_data["password"]).present? || authentication_token.present?
end
def authenticate!
if authentication_token
user = User.find_by_authentication_token authentication_token
user.nil? ? fail!() : success!(user)
else
user = User.find_by_email params["session"]["email"]
if user.nil? || user.password != params["session"]["password"]
fail!()
else
success! user
end
end
end
private
def authentication_token
params["authentication_token"]
end
end
Bây giờ chúng ta cần add strategies vào warden
config/application.rb
config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
manager.default_strategies :password_strategy
# Nếu có thêm strategy thì bạn chỉ việc thêm tên strategy vào sau dòng trên
# ví dụ
# manager.default_strategies :password_strategy, :basic_auth
end
config/initialize/warden.rb
require Rails.root.join('lib/strategies/password_strategy')
Warden::Strategies.add(:password_strategy, PasswordStrategy)
Giờ chúng ta sẽ tạo 1 helper để thực thi các hàm current_user, logged_in?, authenticate!...
controllers/concern/warden_helper.rb
module WardenHelper
extend ActiveSupport::Concern
included do
helper_method :warden, :logged_in?, :current_user
prepend_before_action :authenticate!
end
def logged_in?
current_user.present?
end
def current_user
warden.user
end
def warden
env['warden']
end
def authenticate!
warden.authenticate! :password_strategy
end
end
Tạo thêm 2 controller session và registration để đăng nhập và đăng ký
controllers/sessions_controller.rb
class SessionsController < ApplicationController
skip_before_filter :authenticate!
def create
authenticate!
redirect_to user_path(current_user)
end
def destroy
warden.logout
redirect_to new_sessions_path
end
end
controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
skip_before_action :authenticate!
def new
@user = User.new
end
def create
@user = User.new user_params
if @user.save
flash[:notice] = "Registrations User Success"
redirect_to new_sessions_path
end
end
Chúng ta cần 1 controller UnauthoziedController đóng vai trò là failure app để báo lỗi khi xảy ra lỗi trong quá trình authenticate
class UnauthorizedController < ActionController::Metal
include ActionController::UrlFor
include ActionController::Redirecting
include Rails.application.routes.url_helpers
include Rails.application.routes.mounted_helpers
delegate :flash, to: :request
class << self
def call env
@respond ||= action :respond
@respond.call env
end
end
def respond
unless request.get?
env["warden.options"][:message] = "Logged in fail!"
flash.alert = env["warden.options"][:message]
end
redirect_to new_sessions_url
end
end
Khai báo cho warden biết là nó có một failure_app
config/application.rb
config.middleware.insert_after ActionDispatch::Flash, Warden::Manager do |manager|
manager.default_strategies :password_strategy
manager.failure_app = UnauthorizedController
end
Một số view khá đơn giản nên mình sẽ không show lên đây. Như vậy với một chút kiến thức mình đã chia sẻ hi vọng mọi người đã phần nào hiểu được về Warden. Một số link tham khảo:
https://github.com/hassox/warden/wiki/Overview http://pothibo.com/2013/07/authentication-with-warden-devise-less/ http://blog.maestrano.com/rails-api-authentication-with-warden-without-devise/ https://github.com/hassox/warden/blob/master/lib/warden/strategies/base.rb
All rights reserved