从 Ruby on Rails 模型中访问 current_user
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1568218/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Access to current_user from within a model in Ruby on Rails
提问by knuton
I need to implement fine-grained access control in a Ruby on Rails app. The permissions for individual users are saved in a database table and I thought that it would be best to let the respective resource (i.e. the instance of a model) decide whether a certain user is allowed to read from or write to it. Making this decision in the controller each time certainly wouldn't be very DRY.
The problem is that in order to do this, the model needs access to the current user, to call something like . Models in general do not have access to session data, though. may_read?(current_user, attribute_name)
我需要在 Ruby on Rails 应用程序中实现细粒度的访问控制。单个用户的权限保存在一个数据库表中,我认为最好让相应的资源(即模型的实例)决定是否允许某个用户读取或写入它。每次在控制器中做出这个决定肯定不会很枯燥。
问题是,为了做到这一点,模型需要访问当前用户,调用类似. 但是,模型通常无法访问会话数据。 may_read?(current_user, attribute_name)
There are quite some suggestions to save a reference to the current user in the current thread, e.g. in this blog post. This would certainly solve the problem.
有很多建议可以在当前线程中保存对当前用户的引用,例如在 这篇博文中。这肯定能解决问题。
Neighboring Google results advised me to save a reference to the current user in the User class though, which I guess was thought up by someone whose application does not have to accommodate a lot of users at once. ;)
不过,邻近的 Google 结果建议我在 User 类中保存对当前用户的引用,我猜这是由其应用程序不必同时容纳大量用户的人想到的。;)
Long story short, I get the feeling that my wish to access the current user (i.e. session data) from within a model comes from me doing it wrong.
长话短说,我觉得我希望从模型中访问当前用户(即会话数据)是因为我做错了。
Can you tell me how I'm wrong?
你能告诉我我怎么错了吗?
采纳答案by gtd
I'd say your instincts to keep current_userout of the model are correct.
我会说你远离current_user模型的直觉是正确的。
Like Daniel I'm all for skinny controllers and fat models, but there is also a clear division of responsibilities. The purpose of the controller is to manage the incoming request and session. The model should be able to answer the question "Can user x do y to this object?", but it's nonsensical for it to reference the current_user. What if you are in the console? What if it's a cron job running?
和 Daniel 一样,我完全支持瘦控制器和胖模型,但也有明确的职责分工。控制器的目的是管理传入的请求和会话。该模型应该能够回答“用户 x 可以对这个对象做 y 吗?”这个问题,但是它引用current_user. 如果你在控制台呢?如果它是一个运行的 cron 作业怎么办?
In many cases with the right permissions API in the model, this can be handled with one-line before_filtersthat apply to several actions. However if things are getting more complex you may want to implement a separate layer (possibly in lib/) that encapsulates the more complex authorization logic to prevent your controller from becoming bloated, and prevent your model from becoming too tightly coupled to the web request/response cycle.
在模型中具有正确权限 API 的许多情况下,这可以通过before_filters适用于多个操作的一行来处理。但是,如果事情变得越来越复杂,您可能需要实现一个单独的层(可能在 中lib/)来封装更复杂的授权逻辑,以防止您的控制器变得臃肿,并防止您的模型与 Web 请求/响应周期耦合得太紧.
回答by Josh K
Although this question has been answered by many I just wanted to add my two cents in quickly.
虽然这个问题已经被很多人回答了,但我只是想快速添加我的两分钱。
Using the #current_user approach on the User model should be implemented with caution due to Thread Safety.
由于线程安全,在 User 模型上使用 #current_user 方法应该谨慎实施。
It is fine to use a class/singleton method on User if you remember to use Thread.current as a way or storing and retrieving your values. But it is not as easy as that because you also have to reset Thread.current so the next request does not inherit permissions it shouldn't.
如果您记得使用 Thread.current 作为一种方式或存储和检索您的值,则可以在 User 上使用类/单例方法。但这并不那么容易,因为您还必须重置 Thread.current 以便下一个请求不会继承它不应该继承的权限。
The point I am trying to make is, if you store state in class or singleton variables, remember that you are throwing thread safety out the window.
我想说明的一点是,如果您将状态存储在类或单例变量中,请记住您将线程安全抛到了窗外。
回答by Nathan Long
The Controller should tell the model instance
控制器应该告诉模型实例
Working with the database is the model's job. Handling web requests, including knowing the user for the current request, is the controller's job.
使用数据库是模型的工作。处理 Web 请求,包括了解当前请求的用户,是控制器的工作。
Therefore, if a model instance needs to know the current user, a controller should tell it.
因此,如果模型实例需要知道当前用户,控制器应该告诉它。
def create
@item = Item.new
@item.current_user = current_user # or whatever your controller method is
...
end
This assumes that Itemhas an attr_accessorfor current_user.
这假设Item有一个attr_accessorfor current_user。
(Note - I first posted this answer on another question,but I've just noticed that question is a duplicate of this one.)
回答by Hesse
I'm all in for skinny controller & fat models, and I think auth shouldn't break this principle.
我完全支持瘦控制器和胖模型,我认为 auth 不应该违反这个原则。
I've been coding with Rails for an year now and I'm coming from PHP community.For me, It's trivial solution to set the current user as "request-long global". This is done by default in some frameworks, for example:
我已经用 Rails 编码一年了,我来自 PHP 社区。对我来说,将当前用户设置为“request-long global”是一个微不足道的解决方案。这是在某些框架中默认完成的,例如:
In Yii, you may access the current user by calling Yii::$app->user->identity. See http://www.yiiframework.com/doc-2.0/guide-rest-authentication.html
在 Yii 中,您可以通过调用 Yii::$app->user->identity 来访问当前用户。见http://www.yiiframework.com/doc-2.0/guide-rest-authentication.html
In Lavavel, you may also do the same thing by calling Auth::user(). See http://laravel.com/docs/4.2/security
在 Lavavel 中,你也可以通过调用 Auth::user() 来做同样的事情。请参阅http://laravel.com/docs/4.2/security
Why if I can just pass the current user from controller??
为什么我只能从控制器传递当前用户?
Let's assume that we are creating a simple blog application with multi-user support. We are creating both public site (anon users can read and comment on blog posts) and admin site (users are logged in and they have CRUD access to their content on the database.)
假设我们正在创建一个具有多用户支持的简单博客应用程序。我们正在创建公共站点(匿名用户可以阅读和评论博客文章)和管理站点(用户已登录并且他们对数据库中的内容具有 CRUD 访问权限。)
Here's "the standard ARs":
这是“标准 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
Now, on the public site:
现在,在公共网站上:
class PostsController < ActionController::Base
def index
# Nothing special here, show latest posts on index page.
@posts = Post.includes(:comments).latest(10)
end
end
That was clean & simple. On the admin site however, something more is needed. This is base implementation for all admin controllers:
那是干净和简单的。然而,在管理站点上,需要更多的东西。这是所有管理控制器的基本实现:
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
So login functionality was added and the current user gets saved to a global. Now User model looks like this:
因此添加了登录功能并将当前用户保存到全局。现在用户模型看起来像这样:
# 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 is now thread-safe
User.current 现在是线程安全的
Let's extend other models to take advantage of this:
让我们扩展其他模型以利用这一点:
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
Post model was extended so that it feels like there's only currently logged in user's posts. How cool is that!
帖子模型已扩展,因此感觉就像只有当前登录的用户帖子。多么酷啊!
Admin post controller could look something like this:
Admin post 控制器可能看起来像这样:
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
For Comment model, the admin version could look like this:
对于评论模型,管理员版本可能如下所示:
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: This is powerful & secure way to make sure that users can access / modify only their data, all in one go. Think about how much developer needs to write test code if every query needs to start with "current_user.posts.whatever_method(...)"? A lot.
恕我直言:这是一种强大且安全的方式,可确保用户一次性访问/修改他们的数据。想想如果每个查询都需要以“current_user.posts.whatever_method(...)”开头,那么开发人员需要编写多少测试代码?很多。
Correct me if I'm wrong but I think:
如果我错了,请纠正我,但我认为:
It's all about separation of concerns. Even when it's clear that only controller should handle the auth checks, by no means the currently logged in user should stay in the controller layer.
这完全是关于关注点分离。即使很明显只有控制器应该处理身份验证检查,当前登录的用户也不应该留在控制器层。
Only thing to remember: DO NOT overuse it! Remember that there may be email workers that are not using User.current or you maybe accessing the application from a console etc...
唯一要记住的是:不要过度使用它!请记住,可能存在未使用 User.current 的电子邮件工作人员,或者您可能从控制台等访问应用程序...
回答by armchairdj
Ancient thread, but worth noting that starting in Rails 5.2, there's a baked-in solution to this: the Current model singleton, covered here: https://evilmartians.com/chronicles/rails-5-2-active-storage-and-beyond#current-everything
古老的线程,但值得注意的是,从 Rails 5.2 开始,有一个成熟的解决方案:当前模型单例,这里介绍:https: //evilmartians.com/chronicles/rails-5-2-active-storage-and -超越#当前-一切
回答by khelll
Well my guess here is that current_useris finally a User instance, so, why don't u add these permissions to the Usermodel or to the data model u want to have the permissions to be applied or queried?
好吧,我的猜测是这current_user最终是一个 User 实例,那么,为什么不将这些权限添加到User模型或数据模型中,您希望拥有应用或查询的权限?
My guess is that u need to restructure your model somehow and pass the current user as a param, like doing:
我的猜测是您需要以某种方式重构您的模型并将当前用户作为参数传递,例如:
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
回答by keredson
I'm always amazed at "just don't do that" responses by people who know nothing of the questioner's underlying business need. Yes, generally this should be avoided. But there are circumstances where it's both appropriate and highly useful. I just had one myself.
我总是对那些对提问者的潜在业务需求一无所知的人的“不要那样做”的回答感到惊讶。是的,通常应该避免这种情况。但在某些情况下,它既合适又非常有用。我自己只有一个。
Here was my solution:
这是我的解决方案:
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
This walks the stack backwards looking for a frame that responds to current_user. If none is found it returns nil. It could be made more robust by confirming the expected return type, and possibly by confirming owner of the frame is a type of controller, but generally works just dandy.
这将向后遍历堆栈以寻找响应 的帧current_user。如果没有找到,则返回 nil。通过确认预期的返回类型,并可能通过确认框架的所有者是一种控制器,它可以变得更加健壮,但通常只是花花公子。
回答by jimfish
I have this in an application of mine. It simply looks for the current controllers session[:user] and sets it to a User.current_user class variable. This code works in production and is pretty simple. I wish I could say I came up with it, but I believe I borrowed it from an internet genius elsewhere.
我在我的一个应用程序中有这个。它只是查找当前控制器 session[: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
attr_accessor :current_user
end
回答by Daniel Kristensen
I'm using the Declarative Authorization plugin, and it does something similar to what you are mentioning with current_userIt uses a before_filterto pull current_userout and store it where the model layer can get to it. Looks like this:
我正在使用声明性授权插件,它执行的操作与您提到的类似。current_user它使用 abefore_filter将其拉出current_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
I'm not using the model features of Declarative Authorization though. I'm all for the "Skinny Controller - Fat Model" approach, but my feeling is that authorization (as well as authentication) is something that belongs in the controller layer.
不过,我没有使用声明性授权的模型功能。我完全支持“瘦控制器 - 胖模型”方法,但我的感觉是授权(以及身份验证)属于控制器层。
回答by Markus
My feeling is the current user is part of the "context" of your MVC model, think of the current user like of the current time, the current logging stream, the current debugging level, the current transaction etc. You could pass all these "modalities" as arguments into your functions. Or you make it available by variables in a context outside the current function body. Thread local context is the better choice than global or otherwise scoped variables because of easiest thread safety. As Josh K said, the danger with thread locals is that they must be cleared after the task, something a dependency injection framework can do for you. MVC is a somewhat simplified picture of the application reality and not everything is covered by it.
我的感觉是当前用户是 MVC 模型“上下文”的一部分,将当前用户想像为当前时间、当前日志流、当前调试级别、当前事务等。你可以传递所有这些“模态”作为函数的参数。或者您可以通过当前函数体之外的上下文中的变量来使用它。由于最简单的线程安全性,线程局部上下文是比全局变量或其他作用域变量更好的选择。正如 Josh K 所说,线程局部变量的危险在于它们必须在任务完成后被清除,依赖注入框架可以为你做一些事情。MVC 是应用程序实际情况的简化图,它并未涵盖所有内容。

