覆盖设计控制器——只有具有管理员角色的用户才能登录?

Override Devise controller -- only user with admin role to log in?

如何覆盖 Devise 控制器以仅允许 'admins' 登录?

这是我想出的:

class SessionsController < Devise::SessionsController

  def create
    if current_user.admin?
    #   tell the user "you can't do that"
    else
      super
    end
  end

end

但用户能够登录(可能是因为 'current_admin' 尚未定义?)。这是原始设计控制器操作:

class Devise::SessionsController < DeviseController
  prepend_before_filter :require_no_authentication, only: [:new, :create]
  prepend_before_filter :allow_params_authentication!, only: :create
  prepend_before_filter :verify_signed_out_user, only: :destroy
  prepend_before_filter only: [:create, :destroy] { request.env["devise.skip_timeout"] = true }


...


  # POST /resource/sign_in
  def create
    self.resource = warden.authenticate!(auth_options)
    set_flash_message(:notice, :signed_in) if is_flashing_format?
    sign_in(resource_name, resource)
    yield resource if block_given?
    respond_with resource, location: after_sign_in_path_for(resource)
  end

...

end

编辑:我认为我不应该更改会话控制器,我认为我应该向 Warden 添加一个策略。我试过了,它仍然登录非管理员用户:

config/initializers/custom_warden_strategies.rb:

Warden::Strategies.add(:admin_only) do
  def authenticate!
    resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
    encrypted = false
    if validate(resource) { encrypted = true; resource.valid_password?(password) }
      if resource.admin?
        remember_me(resource)
        resource.after_database_authentication
        success!(resource)
      end
    end
    mapping.to.new.password = password if !encrypted && Devise.paranoid
    fail(:not_found_in_database) unless resource
  end
end

config\initializers\devise.rb

  config.warden do |manager|
    manager.default_strategies.unshift :admin_only
  end

我找到了一个解决方案,但它在我的测试套件中失败了(虽然我手动测试它时有效)。

config/initializers/admin_only_initializer.rb

require 'devise/strategies/authenticatable'

module Devise
  module Strategies
    # Default strategy for signing in a user, based on their email and password in the database.
    class AdminOnly < Authenticatable
      def authenticate!
        resource  = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
        encrypted = false

        if validate(resource){ encrypted = true; resource.valid_password?(password) }
          if resource.admin?
            success!(resource)
          else
            fail!(:not_permitted)
          end
        end
        mapping.to.new.password = password if !encrypted && Devise.paranoid
        fail(:not_found_in_database) unless resource
      end
    end
  end
end

Warden::Strategies.add(:admin_only, Devise::Strategies::AdminOnly)

config/initializers/devise.rb

config.warden do |manager|
    manager.default_strategies(:scope => :user).unshift :admin_only
end

和 I18n 字符串 (config/locales/devise.en.yml):

en:
  devise:
    failure:
      not_permitted: "You are not permitted to complete this action."

试一试:

class SessionsController < Devise::SessionsController

  def create
    super do
      if !resource.admin?
        signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
        set_flash_message :notice, :signed_out if signed_out && is_flashing_format?
        respond_to_on_destroy
      end
    end
  end

end

为什么要阻止非管理员登录而不只是阻止非管理员用户的某些操作?那么,您如何区分管理员和普通用户?

请记住,一个真正的安全原则是拒绝然后允许。因此,为了确保您的应用程序在您不断添加内容(例如 AGILE 开发)时保持安全,我建议采用以下方法

application_controller.rb

class ApplicationController
  before_action :authenticate_user!
  # Security policy deny then access
  before_filter :access_denied

  # Actually I have refactorised below code in a separate security.rb module that I include in ApplicationController, but as you wish
  def access_denied
    if @denied and not @authorized
        flash[:alert] = 'Unauthorized access'
        flash[:info] = "Authorized entities : #{@authorized_entities.join(', ')}" if @authorized_entities
        flash[:warning] = "Restricted to entities : #{@restricted_entities.join(', ')}" if @restricted_entities
        render 'static_pages/home', :status => :unauthorized and return
        false
    end
  end

    def allow_access_to_administrators
        (@authorized_entities ||= []) << "Administrators"
        @authorized = true if administrateur_logged_in?
    end

    def administrateur_signed_in?
      user_signed_in? and current_user.administrator? # Or whatever method you use to authenticate admins
    end

end

请注意,我同时使用了@authorized 和@denied。

我通常对 class 的用户(如管理员)使用 @authorized,而我设置 @denied 如果对于 class 的用户,我想限制到一个子集。

那我用

your_controller_reserved_for_admins.rb

prepend_before_filter :allow_access_to_administrators