【问题标题】:Rails - Separate Database per SubdomainRails - 每个子域的单独数据库
【发布时间】:2010-12-08 20:13:23
【问题描述】:

我即将开始编写一个 Rails 应用程序,它允许客户拥有一个单独的子域来访问我们的应用程序。从数据安全的角度考虑,如果每个客户的访问权限真正限制在他们的数据库中,那就太好了,这样,如果生产代码中存在错误,他们将只能访问自己的数据库,而不能访问任何其他的数据库客户。

我知道如何做我想做的事情背后的代码,但我想知道是否有一个我可能会遗漏的更简单的解决方案。您将如何保护客户端数据,以便在出现错误或黑客威胁时,他们的数据不太可能被暴露?

【问题讨论】:

    标签: ruby-on-rails security database-design


    【解决方案1】:

    这是我用于解决这个问题的一些代码:

    application_controller.rb

    before_filter :set_database
    
    helper_method :current_website
    
    # I use the entire domain, just change to find_by_subdomain and pass only the subdomain
    def current_website    
      @website ||= Website.find_by_domain(request.host)
    end
    
    def set_database
      current_website.use_database
    end
    
    # Bonus - add view_path
    def set_paths
      self.prepend_view_path current_website.view_path unless current_website.view_path.blank?
    end
    

    网站.rb

    def use_database
      ActiveRecord::Base.establish_connection(website_connection)
    end
    
    # Revert back to the shared database
    def revert_database
      ActiveRecord::Base.establish_connection(default_connection)
    end
    
    private
    
    # Regular database.yml configuration hash
    def default_connection
      @default_config ||= ActiveRecord::Base.connection.instance_variable_get("@config").dup
    end
    
    # Return regular connection hash but with database name changed
    # The database name is a attribute (column in the database)
    def website_connection
      default_connection.dup.update(:database => database_name)
    end
    

    希望这会有所帮助!

    【讨论】:

    • +1 - 绝对非常有用。你能告诉我这种方法对性能的影响(如果有的话)吗?
    • 抱歉,我没有这方面的基准。我目前只在私人测试版上工作。
    • 有人猜测这是否会对性能造成严重影响?我想为生产站点做类似的事情。
    • 我使用此代码的网站每月获得 20,000 次页面浏览量(无缓存),因此任何性能损失都不会引起注意。如果您每天查看 20,000 多个,我建议您制作一个简单的网站并进行一些压力测试。
    • 我现在已经在生产代码中使用这种方法大约一年了,并没有注意到性能上有显着的影响。我们的网站(它是一个 API)每月收到大约 1,000,000 个请求,我没有看到任何与此相关的性能问题。
    【解决方案2】:

    我找到了一个更简单的解决方案,但假设您为每个子域都有一个数据库:

    application_controller.rb

    before_filter :subdomain_change_database
    
    def subdomain_change_database
      if request.subdomain.present? && request.subdomain != "www"
        # 'SOME_PREFIX_' + is optional, but would make DBs easier to delineate
        ActiveRecord::Base.establish_connection(website_connection('SOME_PREFIX_' + request.subdomain ))
      end
    end
    
    # Return regular connection hash but with database name changed
    # The database name is a attribute (column in the database)
    def website_connection(subdomain)
      default_connection.dup.update(:database => subdomain)
    end
    
    # Regular database.yml configuration hash
    def default_connection
      @default_config ||= ActiveRecord::Base.connection.instance_variable_get("@config").dup
    end
    

    这将切换到类似 mydb_subdomain 的数据库。这是一个完整的替代数据库选项,但它可以非常容易地推出多个版本。

    【讨论】:

      【解决方案3】:

      原来我只是问了一个really similar question,但在开发过程中还有很长的路要走——我已经包含了三个关于如何安全地使用其中的单个数据库的想法。

      【讨论】:

      • 感谢您的链接。给了我一些想法,但看起来这个问题还没有真正好的解决方案。
      【解决方案4】:

      您可以使用不同的环境为每个子域运行一个新的服务器实例。

      但这不会很好地扩展。

      不过,前几个google hits for multiple rails databases 提出了一些新建议。将这些链接中的信息放在一起为单个服务器实例提供了这种完全未经测试的解决方案。

      您需要为您的 databases.yml 中的每个子域添加一个数据库条目。然后将 before_filter 添加到您的应用程序控制器

      更新! 示例动态重新加载数据库配置。不幸的是,没有很好的方法可以在不弄乱服务器内部结构的情况下使更新轨道变宽。因此,每次请求都必须重新加载数据库配置。

      此示例假设 databases.yml 中的数据库条目以子域命名。

      config/database.yml

      login: &login
        adapter: mysql
        username: rails
        password: IamAStrongPassword!
        host:  localhost
      
      production:
        <<: *login
        database: mysite_www
      
      subdomain1:
        <<: *login
        database: mysite_subdomain1
      
      subdomain2:
        <<: *login
        database: mysite_subdomain2
      ...
      

      app/controllers/application_controller.rb 需要'erb' before_filter :switch_db_connection

      def switch_db_connection
        subdomain = request.subdomains.first
        ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(Rails.configuration.database_configuration_file)).result)
        ActiveRecord::Base.establish_connection("mysite_#{subdomain}") 
      end
      

      正如我所说,它完全未经测试。但我没有预见到任何重大问题。如果它不起作用,希望它能让你走上正轨。

      【讨论】:

      • 这样做的问题是,当您添加一个新的子域时,您将需要重新启动所有服务器实例。如果您一次添加很多子域,这将意味着一些停机时间。
      • 在单个服务器实例中使示例动态化。但它仍然未经测试。
      猜你喜欢
      • 2018-06-25
      • 2011-09-26
      • 2011-09-05
      • 2014-01-13
      • 1970-01-01
      • 1970-01-01
      • 2017-06-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多