我需要在Ruby On Rails应用程序中实现细粒度的访问控制。将单个用户的权限保存在数据库表中,我认为最好让各个资源(即模型的实例)决定是否允许某个用户从中读取或写入其中。每次在控制器中做出这个决定肯定不是很干。
问题是,为了做到这一点,该模型需要访问当前用户,以调用类似的内容 may_read?(current_user, attribute_name). 。但是,模型通常无法访问会话数据。

有很多建议保存当前线程中当前用户的引用,例如 这篇博客文章. 。这肯定会解决问题。

邻居的Google结果建议我在用户类中保存对当前用户的引用,我想我的应用程序不必一次容纳很多用户。 )

长话短说,我感觉到我希望从模型中访问当前用户(即会话数据)。 做错了.

你能告诉我我错了吗?

有帮助吗?

解决方案

我会说你的直觉要保留 current_user 外出模型是正确的。

像丹尼尔(Daniel)一样,我全都适用于瘦的控制器和脂肪模型,但责任也明确。控制器的目的是管理传入的请求和会话。该模型应该能够回答“用户x可以对此对象做y?”的问题,但是引用该问题是荒谬的 current_user. 。如果您在控制台中怎么办?如果这是一个Cron的工作怎么办?

在许多情况下,模型中具有正确的权限API,可以单行处理 before_filters 适用于几个动作。但是,如果事情变得越来越复杂,您可能想实现一个单独的层(可能在 lib/)封装更复杂的授权逻辑,以防止您的控制器变得肿,并防止模型与Web请求/响应周期过于紧密耦合。

其他提示

尽管这个问题已经得到了许多人的回答,但我只是想迅速增加我的两美分。

由于线程安全性,应谨慎使用用户模型上的#current_user方法。

如果您记得使用thread.current作为一种方式或存储和检索您的值,则可以在用户上使用类/单例方法。但这并不容易,因为您还必须重置线程。汇总,因此下一个请求不会继承不应的权限。

我要提出的观点是,如果您将状态存储在课堂或单顿变量中,请记住,您将线程安全抛在窗口上。

控制器应告知模型实例

使用数据库是模型的工作。处理Web请求(包括了解当前请求的用户)是控制器的作业。

因此,如果模型实例需要了解当前用户,则控制器应该告诉它。

def create
  @item = Item.new
  @item.current_user = current_user # or whatever your controller method is
  ...
end

这是假设的 Item 有一个 attr_accessor 为了 current_user.

(注意 - 我首先在 另一个问题, 但是我刚刚注意到这个问题是这个问题的重复。)

我全都喜欢瘦的控制器和脂肪模型,我认为Auth不应该打破这一原则。

我已经在Rails编码了一年,我来自PHP社区。 对我来说,将当前用户设置为“请求长全局”是微不足道的解决方案。默认情况下,在某些框架中完成了:例如:

在yii中,您可以通过调用yii :: $ app->用户 - >身份来访问当前用户。看 http://www.yiiframework.com/doc-2.0/guide-rest-authentication.html

在Lavavel中,您也可以通过调用auth :: user()来做同样的事情。看 http://laravel.com/docs/4.2/security

为什么如果我只能从控制器传递当前用户?

假设我们正在创建一个具有多用户支持的简单博客应用程序。我们正在创建两个公共网站(Anon用户可以在博客文章上阅读和评论)和管理站点(用户已登录,他们在数据库中访问其内容。)

这是“标准AR”:

class Post < ActiveRecord::Base
  has_many :comments
  belongs_to :author, class_name: 'User', primary_key: author_id
end

class User < ActiveRecord::Base
  has_many: :posts
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

现在,在公共网站上:

class PostsController < ActionController::Base
  def index
    # Nothing special here, show latest posts on index page.
    @posts = Post.includes(:comments).latest(10)
  end
end

那很简单。但是,在管理站点上,需要更多的东西。这是所有管理员控制器的基础实施:

class Admin::BaseController < ActionController::Base
  before_action: :auth, :set_current_user
  after_action: :unset_current_user

  private

    def auth
      # The actual auth is missing for brievery
      @user = login_or_redirect
    end

    def set_current_user
      # User.current needs to use Thread.current!
      User.current = @user
    end

    def unset_current_user
      # User.current needs to use Thread.current!
      User.current = nil
    end
end

因此,添加了登录功能,并且当前用户被保存到全局。现在,用户模型看起来像这样:

# Let's extend the common User model to include current user method.
class Admin::User < User
  def self.current=(user)
    Thread.current[:current_user] = user
  end

  def self.current
    Thread.current[:current_user]
  end
end

user.current现在是线程安全

让我们扩展其他模型以利用这一点:

class Admin::Post < Post
  before_save: :assign_author

  def default_scope
    where(author: User.current)
  end

  def assign_author
    self.author = User.current
  end
end

邮政模型被扩展了,因此感觉就像当前仅登录用户的帖子。 多么酷啊!

Admin Post Controller可以看起来像这样:

class Admin::PostsController < Admin::BaseController
  def index
    # Shows all posts (for the current user, of course!)
    @posts = Post.all
  end

  def new
    # Finds the post by id (if it belongs to the current user, of course!)
    @post = Post.find_by_id(params[:id])

    # Updates & saves the new post (for the current user, of course!)
    @post.attributes = params.require(:post).permit()
    if @post.save
      # ...
    else
      # ...
    end
  end
end

对于评论模型,管理版本看起来像这样:

class Admin::Comment < Comment
  validate: :check_posts_author

  private

    def check_posts_author
      unless post.author == User.current
        errors.add(:blog, 'Blog must be yours!')
      end
    end
end

IMHO:这是确保用户可以一次访问 /修改数据的强大和安全方法。考虑一下如果每个查询都需要从“ current_user.posts.posts.thatey_method(...)”开始,请考虑每个查询需要编写测试代码多少?很多。

如果我错了,请纠正我,但我认为:

这一切都是关于关注的分离。即使很明显,只有控制器才能处理验证检查,绝不应该在控制器层中保留当前登录的用户。

唯一要记住的事情:不要过度使用它!请记住,可能有一些电子邮件工人不使用user.current.current,或者您可能会从控制台等访问应用程序...

古老的线,但值得注意的是,从Rails 5.2开始,有一个烘焙解决方案:当前的模型Singleton,这里涵盖了: https://evilmartians.com/chronicles/rails-5-2-actorive-storage-and-beyond#current-verything

好吧,我的猜测是 current_user 最终是一个用户实例,所以,为什么不将这些权限添加到 User 模型还是您想要应用或查询的权限?

我的猜测是,您需要以某种方式重组模型并将当前用户传递为参数,例如这样做:

class Node < ActiveRecord
  belongs_to :user

  def authorized?(user)
    user && ( user.admin? or self.user_id == user.id )
  end
end

# inside controllers or helpers
node.authorized? current_user

我在我的应用中有这个。它只需查找当前控制器会话[:用户],然后将其设置为user.current_user类变量。该代码在生产中起作用,非常简单。我希望我可以说我想出了它,但我相信我从其他地方的互联网天才借了它。

class ApplicationController < ActionController::Base
   before_filter do |c|
     User.current_user = User.find(c.session[:user]) unless c.session[:user].nil?  
   end
end

class User < ActiveRecord::Base
  cattr_accessor :current_user
end

我总是惊讶于对提问者基本业务需求一无所知的人的回答。是的,通常应该避免这种情况。但是在某些情况下,它既适当又非常有用。我只是一个人。

这是我的解决方案:

def find_current_user
  (1..Kernel.caller.length).each do |n|
    RubyVM::DebugInspector.open do |i|
      current_user = eval "current_user rescue nil", i.frame_binding(n)
      return current_user unless current_user.nil?
    end
  end
  return nil
end

这将堆栈向后走,寻找响应的框架 current_user. 。如果没有发现它将返回零。通过确认预期的返回类型,可以使它变得更加健壮,并且可能通过确认框架的所有者是一种控制器,但通常工作只是花花公子。

我正在使用声明性授权插件,它做的类似于您提到的内容 current_user 它使用一个 before_filtercurrent_user 将其存储在模型层可以到达的位置。看起来这样:

# set_current_user sets the global current user for this request.  This
# is used by model security that does not have access to the
# controller#current_user method.  It is called as a before_filter.
def set_current_user
  Authorization.current_user = current_user
end

我不使用声明授权的模型功能。我全都是“瘦控制器 - 胖模型”方法,但是我的感觉是授权(以及身份验证)是属于控制器层的东西。

我的感觉是当前的用户是MVC模型的“上下文”的一部分,想想当前用户,例如当前时间,当前日志流,当前的调试级别,当前交易等。您可以通过所有这些”模式为“作为您功能的参数。或者,您可以在当前功能主体之外的上下文中通过变量可用。线程本地上下文是比全局或其他范围范围变量更好的选择,因为线程安全性最简单。正如乔什·K(Josh K)所说,当地人的危险是必须在任务后清除它们,这是依赖注入框架可以为您做的事情。 MVC是应用现实的简化图片,并非所有内容都涵盖了它。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top