【问题标题】:Authorization in multi tenant app多租户应用中的授权
【发布时间】:2014-05-09 21:29:21
【问题描述】:

在 Railscasts Episode 388 - Multitenancy with Scopes 中,Ryan 正在添加默认范围以确保安全:

或者,我们可以使用 CanCan 等授权库来处理范围,但这不是为多租户应用程序设计的,它不能很好地解决这个问题。这是可以使用默认范围的一种情况,这就是我们要做的。

class Tenant < ActiveRecord::Base
  attr_accessible :name, :subdomain
  has_many :topics
end

class Topic < ActiveRecord::Base
  attr_accessible :name, :content
  belongs_to :user
  has_many :posts

  default_scope { where(tenant_id: Tenant.current_id) }
end

我的问题是:我想实现授权(例如使用 Cancan)并希望定义如下能力:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)
    if user.admin?
      can :manage, Topic
    else
      can :read, Topic
    end
  end
end

用户是否有能力管理所有租户的主题或仅在租户范围内?

或者更一般的问题:多租户应用程序的正确授权方法是什么?

【问题讨论】:

    标签: ruby-on-rails authorization cancan multi-tenant


    【解决方案1】:

    我认为使用 CanCan 或 CanCanCan 是正确的,因为 CanCan 已被弃用。

    我不喜欢default_scope,因为它不是threadsafe。用户 id 存储在一个类变量中,这意味着您的应用程序中的两个或多个并发用户将破坏这一点,除非您使用 Unicorn 或其他确保不超过一个客户端连接访问同一线程的 Web 服务器.

    因此你应该使用像 Cancan 这样的东西。

    class Ability
      include CanCan::Ability
    
      def initialize(user)
        user ||= User.new # guest user (not logged in)
        if user.admin?
          # User's own Topics only:
          can :manage, Topic, user_id: user.id
          # or, with a Tenant
          can :manage, Topic, tenant_id: user.tenant.id if user.tenant # User belongs_to Tenant
          can :manage, Topic, tenant_id: user.tenants.map(&:id) if user.tenants.any? # User has_many Tenants
        else
          can :read, Topic # Anyone can read any topic.
        end
      end
    end
    

    从以上三个示例中选择您需要的策略。


    编辑在 cmets 中针对 @JoshDoody 的问题的多租户管理员的稍微复杂的示例:


    class Admin < User; end
    
    class TenantAdmin
      belongs_to :tenant
      belongs_to :admin, class_name: User
    end
    
    class Ability
      include CanCan::Ability
    
      def initialize(user)
        user ||= User.new # guest user (not logged in)
        if user.admin?
          can :manage, Topic, tenant_id: TenantAdmin.where(admin: user).map(&:tenant_id)
        else
          can :read, Topic # Anyone can read any topic
        end
      end
    end
    

    现在,这可能不是您想要的性能,但总体思路是您的租户管理员将能够管理其租户中的主题。

    希望这会有所帮助。

    【讨论】:

    • 这是一个很好的答案。这如何寻找一个多租户应用程序,其中用户可以在每个租户中拥有不同的角色。因此,用户不仅仅是管理员,而是租户的管理员,并且可能不是其他租户的管理员。我会说您的第三个示例为“如果用户是管理员,则用户可以管理他所有租户的主题”。因此,该示例假定用户是整个应用程序的管理员,因此只要他是租户的成员,就可以管理应用程序中所有租户的主题。但我想知道怎么说,“用户可以为那些他是管理员的租户管理主题。”想法?
    • @JoshDoody 我用某种针对您的案例的解决方案更新了答案。再次,希望这会有所帮助。
    • 这很有帮助 - 谢谢!我实际上建立了一些我正在尝试的东西(它与你的相似),我稍后会尝试发布它来看看你的想法。 (我将在我的代码中添加另一个答案。)但这很好而且很有帮助 - 谢谢!
    • 我只是在谷歌上搜索同样的问题并碰到了这个旧线程。所以我显然没有很好地解决我的问题。 :) 关于您最后一个解决方案的问题(您专门为解决我的用例而编辑):您建议类似的东西从 default_scopes 切换到在 CanCan 中专门处理这个问题,对吗?因此,如果操作正确,所有 default_scope { where(tenant_id: Tenant.current_id) } 都将被 CanCan 功能替换,类似于您在编辑示例中显示的功能?如果是这样,您如何处理基于 Tenant.current_id 的基本过滤(在控制器中)?
    • @wrdevos 在 railscast Ryan 说:“我们可以在租户模型中找到另一个潜在问题,我们为 current_id 属性调用 cattr_accessor。虽然这很方便,但它并不是真正的线程安全,所以我们可能想要改为这样做:Thread.current[:tenant_id] = id,现在我们有了 getter 和 setter 方法,它们使用 Thread.current 来设置更线程安全的值”。你仍然觉得在这个实现中使用 default_scope 不是线程安全的吗?
    【解决方案2】:

    你已经为主题设置了能力。因此,它只会检查主题对象。

    要检查租户级别,您需要设置如下内容:

    class Ability
      include CanCan::Ability
    
      def initialize(user)
        user ||= User.new # guest user (not logged in)
        can :manage, Tenant do |tenant|
          if user.admin?
            `you code goes here`
          else
          end
        end
        can :read, Tenant
        can :read, Topic
      end
    end
    

    【讨论】:

      【解决方案3】:

      Му 多租户应用能力示例

      class Ability
        def initialize(admin,  tenant = nil)
           user ||= User.new
           if user.admin?
             can :manage, Topic
           else
             can :manage, PostState, tenant: tenant
           end
        end
      end
      

      您不能通过租户并使用Tenant.current_id

      【讨论】:

        猜你喜欢
        • 2017-10-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-11
        • 2018-10-11
        • 2021-03-06
        • 1970-01-01
        相关资源
        最近更新 更多