knu*_*ton 69 ruby session ruby-on-rails rails-activerecord
我需要在Ruby on Rails应用程序中实现细粒度的访问控制.单个用户的权限保存在数据库表中,我认为最好让相应的资源(即模型的实例)决定是否允许某个用户从中读取或写入.每次在控制器中做出这个决定肯定不会很干.
问题是,为了做到这一点,模型需要访问当前用户,调用类似的东西.但是,模型通常无法访问会话数据. may_read?(current_user, attribute_name)
有一些建议可以在当前线程中保存对当前用户的引用,例如在 此博客文章中.这肯定会解决问题.
相邻的Google搜索结果建议我在User类中保存对当前用户的引用,我想这应该是那些应用程序不必同时容纳很多用户的人.;)
长话短说,我觉得我希望从模型中访问当前用户(即会话数据)来自我做错了.
你能告诉我我错了吗?
gtd*_*gtd 44
我会说你的直觉current_user超出模型是正确的.
像丹尼尔一样,我只是为了瘦弱的控制者和胖子模特,但也有明确的责任分工.控制器的目的是管理传入的请求和会话.该模型应该能够回答"用户x可以对这个对象做什么吗?"的问题,但它引用它是没有意义的current_user.如果你在控制台怎么办?如果它是一个cron工作运行怎么办?
在许多情况下,在模型中使用正确的权限API,可以使用before_filters适用于多个操作的单行来处理.但是,如果事情变得越来越复杂,您可能希望实现一个单独的层(可能在lib/),它封装了更复杂的授权逻辑,以防止控制器变得臃肿,并防止您的模型与Web请求/响应周期过于紧密耦合.
小智 36
尽管很多人都回答了这个问题,但我想快速加入我的两分钱.
由于线程安全,应谨慎使用用户模型上的#current_user方法.
如果您记得使用Thread.current作为一种方式或存储和检索您的值,那么在User上使用class/singleton方法是很好的.但它并不那么容易,因为你还必须重置Thread.current,所以下一个请求不会继承它不应该的权限.
我想说的是,如果你在类或单例变量中存储状态,请记住你正在抛出窗口中的线程安全性.
Nat*_*ong 29
使用数据库是模型的工作.处理Web请求(包括了解当前请求的用户)是控制器的工作.
因此,如果模型实例需要知道当前用户,则控制器应该告诉它.
def create
@item = Item.new
@item.current_user = current_user # or whatever your controller method is
...
end
Run Code Online (Sandbox Code Playgroud)
这假设Item有一个attr_accessorfor current_user.
(注意 - 我首先在另一个问题上发布了这个答案,但我刚刚注意到这个问题与此问题重复.)
Hes*_*sse 13
我全力以赴为瘦小的控制器和胖子模特,我认为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":
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
Run Code Online (Sandbox Code Playgroud)
现在,在公共网站上:
class PostsController < ActionController::Base
def index
# Nothing special here, show latest posts on index page.
@posts = Post.includes(:comments).latest(10)
end
end
Run Code Online (Sandbox Code Playgroud)
那简洁干净.但是,在管理员网站上,还需要更多内容.这是所有管理控制器的基本实现:
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
Run Code Online (Sandbox Code Playgroud)
因此添加了登录功能,并将当前用户保存到全局.现在用户模型如下所示:
# 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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
邮政模型被扩展,以便感觉只有当前登录用户的帖子.多么酷啊!
管理员帖子控制器看起来像这样:
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
Run Code Online (Sandbox Code Playgroud)
对于Comment模型,管理员版本可能如下所示:
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
Run Code Online (Sandbox Code Playgroud)
恕我直言:这是一种强大而安全的方法,可以确保用户可以一次性访问/修改他们的数据.想想如果每个查询都需要以"current_user.posts.whatever_method(...)"开头,开发人员需要编写多少测试代码?很多.
如果我错了,请纠正我,但我想:
这一切都与关注点分离有关.即使只清楚只有控制器应该处理auth检查,当然登录的用户也不应该留在控制器层.
唯一要记住的事情是:不要过度使用它!请记住,可能有电子邮件工作者没有使用User.current或您可能从控制台等访问应用程序...
arm*_*rdj 10
古老的线程,但值得注意的是,从Rails 5.2开始,有一个解决方案:现在的模型单例,这里介绍:https://evilmartians.com/chronicles/rails-5-2-active-storage-and -BEYOND#电流一切
Pro*_*ton 10
为了更清楚地了解armchairdj 的答案
我在处理Rails 6应用程序时遇到了这个挑战。
这是我解决它的方法:
从 Rails 5.2 开始,你现在可以添加一个神奇的Current单例,它就像一个可以从你的应用程序内的任何地方访问的全局存储。
首先,在您的模型中定义它:
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :user
end
Run Code Online (Sandbox Code Playgroud)
接下来,将用户设置在控制器中的某个位置,使其可以在模型、作业、邮件或任何你想要的地方访问:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_current_user
private
def set_current_user
Current.user = current_user
end
end
Run Code Online (Sandbox Code Playgroud)
现在你可以Current.user在你的模型中调用:
# app/models/post.rb
class Post < ApplicationRecord
# You don't have to specify the user when creating a post,
# the current one would be used by default
belongs_to :user, default: -> { Current.user }
end
Run Code Online (Sandbox Code Playgroud)
或者你可以Current.user在你的表格中调用:
# app/forms/application_registration.rb
class ApplicationRegistration
include ActiveModel::Model
attr_accessor :email, :user_id, :first_name, :last_name, :phone,
def save
ActiveRecord::Base.transaction do
return false unless valid?
# User.create!(email: email)
PersonalInfo.create!(user_id: Current.user.id, first_name: first_name,
last_name: last_name, phone: phone)
true
end
end
end
Run Code Online (Sandbox Code Playgroud)
或者你可以Current.user在你的意见中调用:
# app/views/application_registrations/_form.html.erb
<%= form_for @application_registration do |form| %>
<div class="field">
<%= form.label :email %>
<%= form.text_field :email, value: Current.user.email %>
</div>
<div class="field">
<%= form.label :first_name %>
<%= form.text_field :first_name, value: Current.user.personal_info.first_name %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
Run Code Online (Sandbox Code Playgroud)
注意:您可能会说:“此功能违反了关注点分离原则!” 是的,它确实。如果感觉不对,请不要使用它。
您可以在此处阅读有关此答案的更多信息:当前所有内容
就这样。
我希望这有帮助
我猜这里current_user终于是一个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
Run Code Online (Sandbox Code Playgroud)
这是 2021 年的召唤。从 Rails 5.2 开始,有一个新的全局 API 可以使用,但请谨慎使用,如 API 文档中所述:
https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html
提供线程隔离属性单例的抽象超类,该单例在每次请求之前和之后自动重置。这使您可以让整个系统轻松使用所有每个请求的属性。
需要注意的是:很容易过度使用像 Current 这样的全局单例,从而导致模型混乱。Current 只能用于少数顶级全局变量,例如帐户、用户和请求详细信息。卡在 Current 中的属性或多或少应该被所有请求的所有操作使用。如果您开始将特定于控制器的属性放在那里,就会造成混乱。
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :user
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_current_user
private
def set_current_user
Current.user = current_user
end
end
# and now in your model
# app/models/post.rb
class Post < ApplicationRecord
# You don't have to specify the user when creating a post,
# the current one would be used by default
belongs_to :user, default: -> { Current.user }
end
Run Code Online (Sandbox Code Playgroud)
小智 5
我在我的应用程序中有这个.它只查找当前控制器会话[: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
Run Code Online (Sandbox Code Playgroud)
对于那些对提问者的基本业务需求一无所知的人,我总是对"只是不这样做"的回答感到惊讶.是的,通常应该避免这种情况.但在某些情况下,这既适合又非常有用.我自己只有一个.
这是我的解决方案:
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
Run Code Online (Sandbox Code Playgroud)
这会向后移动堆栈,寻找响应的帧current_user.如果没有找到,则返回nil.通过确认预期的返回类型可以使其更加健壮,并且可能通过确认框架的所有者是一种控制器,但通常只是花花公子.
| 归档时间: |
|
| 查看次数: |
63351 次 |
| 最近记录: |