在Ruby on Rails中从模型中访问current_user

我需要在Ruby on Rails应用程序中实现细粒度的访问控制。 个人用户的权限保存在数据库表中,我认为最好让相应的资源(例如模型的实例)决定是否允许某个用户读取或写入。 每次在控制器中作出这个决定肯定不会很干。
问题是,为了做到这一点,模型需要访问当前用户,调用像may_read ?( current_user , attribute_name ) 。 一般来说,模型不能访问会话数据。

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

相邻的Google结果build议我在User类中保存一个对当前用户的引用,但是我猜这是被某个应用程序不需要一次容纳很多用户的人想到的。 ;)

长话短说,我觉得我希望从模型中访问当前用户(即会话数据)的愿望来自我做错了 。

你能告诉我我错了吗?

我会说你保持current_user不在模型中的直觉是正确的。

就像丹尼尔一样,我都是为了瘦身的控制者和胖胖的模特儿,但是也有明确的职责分工。 控制器的目的是pipe理传入的请求和会话。 该模型应该能够回答“用户可以对此对象执行操作吗?”的问题,但是它引用current_user是无意义的。 如果你在控制台呢? 如果这是一个cron工作正在运行?

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

虽然这个问题已经得到了很多人的回答,但我只是想快速增加我的两分钱。

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

如果你记得使用Thread.current作为一种方式或者存储和检索你的值,那么在User上使用类/单例方法是很好的。 但是这并不容易,因为你也必须重置Thread.current,所以下一个请求不会inheritance它不应该拥有的权限。

我试图做的一点是,如果你在class或singletonvariables中存储状态,请记住你正在把线程安全性扔出窗外。

控制器应该告诉模型实例

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

因此,如果一个模型实例需要知道当前用户,一个控制器应该告诉它。

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

这假定Item有一个用于current_userattr_accessor

(注意 – 我首先在另一个问题上发布了这个答案,但是我刚刚注意到这个问题是这个问题的重复。)

我全身心投入瘦身控制器和胖子模型,我认为auth不应该违反这个原则。

我已经使用Rails编写了一年,现在我来自PHP社区。 对我而言,将当前用户设置为“请求全局”是一个简单的解决scheme。 这是默认在一些框架中完成的,例如:

在Yii中,你可以通过调用Yii :: $ app-> user-> identity来访问当前用户。 见http://www.yiiframework.com/doc-2.0/guide-rest-authentication.html

在Lavavel中,你也可以通过调用Auth :: user()来做同样的事情。 请参阅http://laravel.com/docs/4.2/security

为什么如果我只能通过控制器的当前用户?

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

这里是“标准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 

那很干净简单。 但是,在pipe理网站上,还需要更多的东西。 这是所有pipe理员控制器的基本实现:

 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 

所以loginfunction被添加,当前用户被保存到全局。 现在用户模型如下所示:

 # 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 

后期模型已经扩展,所以感觉只有当前login用户的post。 多么酷啊!

pipe理员后控制器可能看起来像这样:

 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 

对于评论模式,pipe理员版本可能如下所示:

 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 

恕我直言:这是强大和安全的方式,以确保用户只能访问/修改他们的数据,一气呵成。 想想如果每个查询都需要以“current_user.posts.whatever_method(…)”开头,那么开发人员需要编写多lesstesting代码呢? 很多。

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

这是关于分离的关注。 即使很明显,只有控制器应该处理authentication检查,绝不会将当前login的用户留在控制器层。

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

那么我的猜测是, current_user最终是一个用户实例,那么,为什么不把这些权限添加到User模型或数据模型,你想有权限被应用或查询?

我的猜测是,你需要以某种方式重构模型,并将当前用户作为parameter passing,比如:

 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]并将其设置为User.current_user类variables。 此代码在生产中工作,非常简单。 我希望我能说我想出了它,但我相信我从别处的互联网天才借来的。

 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 

对于那些对提问者的潜在业务需求一无所知的人,我总是惊讶于“不这样做”。 是的,一般应该避免。 但是在某些情况下,这两者都是合适的,而且非常有用。 我自己有一个。

这是我的解决scheme:

 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的框架。 如果没有发现它返回零。 通过确认预期的回报types,可以使其更稳健,并且可能通过确认框架的所有者是一种控制器,但通常只是花花公子。

我正在使用Declarative Authorization插件,并且它执行与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 

虽然我没有使用声明性授权的模型特征。 我都是“Skinny Controller-Fat Model”方法,但我的感觉是,授权(以及authentication)属于控制器层。

我的感觉是,当前用户是你的MVC模型的“上下文”的一部分,想当前用户的当前时间,当前日志stream,当前debugging级别,当前事务等。你可以通过所有这些“forms“作为你的职能的论据。 或者你可以通过当前函数体外的上下文中的variables来使用它。 由于最简单的线程安全性,线程本地上下文比全局或其他范围的variables更好。 正如Josh K所说的,线程本地化的危险在于它们必须在任务之后被清除,dependency injection框架可以为你做的事情。 MVC是一个简单的应用程序现实的图片,并不是所有的东西都包含在内。