Rails:从 Ruby on Rails 中的模型内访问 current_user
我需要在 Ruby on Rails 应用程序中实现细粒度的访问控制。各个用户的权限保存在数据库表中,我认为最好让相应的资源(即模型的实例)决定是否允许某个用户读取或写入它。每次在控制器中做出这个决定肯定不会很干燥。
问题是,为了做到这一点,模型需要访问当前用户,调用类似 may_read
?(current_user
, attribute_name< /代码>)。不过,模型通常无法访问会话数据。
有很多建议在当前线程中保存对当前用户的引用,例如在 这篇博文。这肯定能解决问题。
附近的 Google 结果建议我在 User 类中保存对当前用户的引用,我猜这是由应用程序不必同时容纳大量用户的人想到的。 ;)
长话短说,我感觉我希望从模型中访问当前用户(即会话数据)来自于我 做错了。
你能告诉我我哪里错了吗?
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
)
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. ;)
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
我想说,您将
current_user
排除在模型之外的直觉是正确的。和丹尼尔一样,我也赞成瘦控制者和胖模型,但也有明确的职责分工。控制器的目的是管理传入的请求和会话。该模型应该能够回答“用户 x 可以对此对象执行 y 操作吗?”的问题,但引用
current_user
是没有意义的。如果你在控制台怎么办?如果它正在运行一个 cron 作业怎么办?在许多情况下,在模型中使用正确的权限 API 时,可以使用适用于多个操作的一行
before_filters
来处理此问题。但是,如果事情变得更加复杂,您可能需要实现一个单独的层(可能在 lib/ 中)来封装更复杂的授权逻辑,以防止您的控制器变得臃肿,并防止您的模型变得过于臃肿。与网络请求/响应周期紧密耦合。I'd say your instincts to keep
current_user
out of the model are correct.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?In many cases with the right permissions API in the model, this can be handled with one-line
before_filters
that apply to several actions. However if things are getting more complex you may want to implement a separate layer (possibly inlib/
) 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.控制器应该告诉模型实例
使用数据库是模型的工作。处理 Web 请求,包括了解当前请求的用户,是控制器的工作。
因此,如果模型实例需要知道当前用户,控制器应该告诉它。
这假设
Item
具有current_user
的attr_accessor
。(注意 - 我首先在另一个问题上发布了这个答案,但我刚刚注意到该问题与此问题重复。)
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.
Therefore, if a model instance needs to know the current user, a controller should tell it.
This assumes that
Item
has anattr_accessor
forcurrent_user
.(Note - I first posted this answer on another question, but I've just noticed that question is a duplicate of this one.)
虽然这个问题已经有很多人回答了,但我只是想快速添加我的两分钱。
由于线程安全性,在用户模型上使用 #current_user 方法应谨慎实施。
如果您记得使用 Thread.current 作为存储和检索值的方式,那么可以在 User 上使用类/单例方法。但这并不那么容易,因为您还必须重置 Thread.current,以便下一个请求不会继承它不应该继承的权限。
我想说的一点是,如果您将状态存储在类或单例变量中,请记住您正在将线程安全性抛到九霄云外。
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.
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.
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.
为了更清楚地了解 armchairdj 的答案,
我在开发 Rails 6 应用程序时遇到了这一挑战。
我是这样解决的:
从 Rails 5.2 开始,我们现在可以添加一个神奇的
Current
单例,它的作用就像一个可以从应用程序内的任何位置访问的全局存储。首先,在您的模型中定义它:
接下来,在控制器中的某个位置设置用户,使其可以在模型、作业、邮件或任何您想要的地方访问:
现在您可以在您的模型中调用
Current.user
:或者您可以在表单中调用
Current.user
:或者您可以在视图中调用
Current.user
:注意:您可能会说:“此功能违反了关注点分离原则! ”是的,确实如此。如果感觉不对,请勿使用它。
您可以在这里阅读有关此答案的更多信息: 当前所有内容
就这些了。
我希望这有帮助
To shed more light on armchairdj's answer
I faced this challenge when working on a Rails 6 application.
Here's how I solved it:
From Rails 5.2 you now we can add a magic
Current
singleton which acts like a global store accessible from anywhere inside your app.First, define it in your models:
Next, set the user somewhere in your controller to make it accessible in models, jobs, mailers, or wherever you want:
Now you can call the
Current.user
in your models:OR you can call the
Current.user
in your forms:OR you can call the
Current.user
in your views:Note: You may say: “This feature breaks the Separation of Concerns principle!” Yes, it does. Don’t use it if it feels wrong.
You can read more about this answer here: Current everything
That's all.
I hope this helps
古老的线程,但值得注意的是,从 Rails 5.2 开始,有一个内置的解决方案:当前模型单例,此处介绍:https://evilmartians.com/chronicles/rails-5-2-active-storage-and-beyond#current-everything
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
我完全喜欢瘦控制器和胖模型,我认为 auth 不应该打破这个原则。
我已经使用 Rails 编码一年了,我来自 PHP 社区。对我来说,将当前用户设置为“请求长全局”是一个简单的解决方案。在某些框架中这是默认完成的,例如:
在 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
为什么我可以只需从控制器传递当前用户?
让我们假设我们正在创建一个具有多用户支持的简单博客应用程序。我们正在创建公共站点(匿名用户可以阅读和评论博客文章)和管理站点(用户已登录,并且可以对数据库中的内容进行 CRUD 访问。)
这是“标准 AR”:
现在,在公共站点上网站:
那很干净&简单的。然而,在管理站点上,还需要更多东西。这是所有管理控制器的基本实现:
因此添加了登录功能,并将当前用户保存到全局中。现在 User 模型看起来像这样:
User.current 现在是线程安全的
让我们扩展其他模型以利用这一点:
Post 模型已扩展,因此感觉好像只有当前登录的用户的帖子。 这太酷了!
管理帖子控制器可能看起来像这样:
对于评论模型,管理版本可能看起来像这样:
恕我直言:这很强大,而且很强大。确保用户只能一次性访问/修改其数据的安全方式。想想如果每个查询都需要以“current_user.posts.whatever_method(...)”开头,开发人员需要编写多少测试代码?很多。
如果我错了,请纠正我,但我认为:
这都是关于关注点分离的。即使很明显只有控制器应该处理身份验证检查,当前登录的用户也不应该留在控制器层。
唯一要记住的是:不要过度使用它!请记住,可能有电子邮件工作人员不使用 User.current 或者您可能从控制台等访问该应用程序......
I'm all in for skinny controller & fat models, and I think auth shouldn't break this principle.
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:
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
In Lavavel, you may also do the same thing by calling Auth::user(). See 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.)
Here's "the standard ARs":
Now, on the public site:
That was clean & simple. On the admin site however, something more is needed. This is base implementation for all admin controllers:
So login functionality was added and the current user gets saved to a global. Now User model looks like this:
User.current is now thread-safe
Let's extend other models to take advantage of this:
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:
For Comment model, the admin version could look like this:
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.
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...
这是 2021 年的召唤。从 Rails 5.2 开始,有一个新的全局 API 可以使用,但请谨慎使用,如 API 文档中所述:
https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html
This is 2021 calling. Since rails 5.2 there's a new global API that can be used, but use it with caution as stated in the API docs:
https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html
好吧,我的猜测是
current_user
最终是一个 User 实例,所以,为什么不将这些权限添加到User
模型或您想要的数据模型中要应用或查询的权限?我的猜测是你需要以某种方式重组你的模型并将当前用户作为参数传递,就像这样做:
Well my guess here is that
current_user
is finally a User instance, so, why don't u add these permissions to theUser
model or to the data model u want to have the permissions to be applied or queried?My guess is that u need to restructure your model somehow and pass the current user as a param, like doing:
我总是对那些对提问者的潜在业务需求一无所知的人的“不要这样做”的回答感到惊讶。是的,通常应该避免这种情况。但在某些情况下它既合适又非常有用。我自己也只有一张。
这是我的解决方案:
这会向后遍历堆栈,查找响应
current_user
的帧。如果没有找到,则返回 nil。通过确认预期的返回类型,并且可能通过确认框架的所有者是一种控制器,可以使其更加健壮,但通常工作得很好。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:
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
中提到的类似,它使用before_filter
来拉出current_user
并将其存储在模型层可以访问的地方。看起来像这样:不过,我没有使用声明性授权的模型功能。我完全赞成“瘦控制器 - 胖模型”方法,但我的感觉是授权(以及身份验证)属于控制器层。
I'm using the Declarative Authorization plugin, and it does something similar to what you are mentioning with
current_user
It uses abefore_filter
to pullcurrent_user
out and store it where the model layer can get to it. Looks like this: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.
我的应用程序中有这个。它只是查找当前控制器 session[:user] 并将其设置为 User.current_user 类变量。这段代码可以在生产中使用并且非常简单。我希望我能说我想出了它,但我相信我是从其他地方的互联网天才那里借来的。
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.
我的感觉是当前用户是 MVC 模型“上下文”的一部分,将当前用户想象为当前时间、当前日志流、当前调试级别、当前事务等。您可以传递所有这些“ modalities”作为函数的参数。或者您可以通过当前函数体之外的上下文中的变量来使其可用。由于最简单的线程安全性,线程局部上下文是比全局变量或其他作用域变量更好的选择。正如 Josh K 所说,线程局部变量的危险在于必须在任务完成后清除它们,这是依赖注入框架可以为您做的事情。 MVC 是应用程序现实的某种简化图景,但它并未涵盖所有内容。
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.
我参加这个聚会已经很晚了,但是如果您需要细粒度的访问控制或具有复杂的权限,我肯定会推荐 Cancancan Gem:
https://github.com/CanCanCommunity/cancancan
它允许您定义您的每个操作的权限控制器可以控制您想要的任何内容,并且由于您在任何控制器上定义了当前能力,因此您可以发送所需的任何参数,例如
current_user
。您可以在ApplicationController
中定义通用的current_ability
方法并自动设置:这样,您就可以将一个
UserAbilities
类链接到您的 UserController, PostAbilities 到你的 PostController 等等。然后在那里定义复杂的规则:这是一个很棒的宝石!希望有帮助!
I am so very late to this party, but if you need fine-grained access control or have complex permissions I would definitely recommend the Cancancan Gem:
https://github.com/CanCanCommunity/cancancan
It allows you to define permissions at every action in your Controller to whatever you want, and since you define the current ability on any controller you can send any parameters you need, like the
current_user
. You can define a genericcurrent_ability
method inApplicationController
and set up things automagically:This way, you can have a
UserAbilities
Class linked to your UserController, PostAbilities to your PostController and so on. And then define the complex rules in there:It's a great Gem! hope it helps!