从ruby on Rails中的模型中访问Current_user
-
21-09-2019 - |
题
我需要在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_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
我不使用声明授权的模型功能。我全都是“瘦控制器 - 胖模型”方法,但是我的感觉是授权(以及身份验证)是属于控制器层的东西。
我的感觉是当前的用户是MVC模型的“上下文”的一部分,想想当前用户,例如当前时间,当前日志流,当前的调试级别,当前交易等。您可以通过所有这些”模式为“作为您功能的参数。或者,您可以在当前功能主体之外的上下文中通过变量可用。线程本地上下文是比全局或其他范围范围变量更好的选择,因为线程安全性最简单。正如乔什·K(Josh K)所说,当地人的危险是必须在任务后清除它们,这是依赖注入框架可以为您做的事情。 MVC是应用现实的简化图片,并非所有内容都涵盖了它。