Protecting the content of public/ in a Rails app

后端 未结 5 1122
刺人心
刺人心 2020-12-23 17:48

I\'m maintaining a Rails app that has content in the public/ folder that will now need to be protected by a login. We\'re considering moving those folders of files into a p

5条回答
  •  臣服心动
    2020-12-23 18:27

    I've done this on a site where people pay to download certain files, and the files are stored in RAILS_ROOT/private. The first thing to know is that you want the web server to handle sending the file, otherwise your app will be held up transmitting large files and this will quickly bring your site to a halt if you have any kind of download volume. So, if you need to check authorization in a controller, then you also need a way to pass control of the download back to the web server. The best way of doing this (that I know of) is the X-Sendfile header, which is supported by Nginx, Apache (with module), and others. With X-Sendfile configured, when your web server receives a X-Sendfile header from your app, it takes over sending the file to the client.

    Once you have X-Sendfile working for your web server, a private controller method like this is helpful:

    ##
    # Send a protected file using the web server (via the x-sendfile header).
    # Takes the absolute file system path to the file and, optionally, a MIME type.
    #
    def send_file(filepath, options = {})
      options[:content_type] ||= "application/force-download"
      response.headers['Content-Type'] = options[:content_type]
      response.headers['Content-Disposition'] = "attachment; filename=\"#{File.basename(filepath)}\""
      response.headers['X-Sendfile'] = filepath
      response.headers['Content-length'] = File.size(filepath)
      render :nothing => true
    end
    

    Then your controller action could look something like this:

    ##
    # Private file download: check permission first.
    #
    def download
      product = Product.find_by_filename!(params[:filename])
      if current_user.has_bought?(product) or current_user.is_superuser?
        if File.exist?(path = product.filepath)
          send_file path, :content_type => "application/pdf"
        else
          not_found
        end
      else
        not_authorized
      end
    end
    

    Obviously your authorization method will vary and you'll need to change the headers if you're offering files other than PDFs or you want the file to be viewed in the browser (get rid of application/force-download content type).

提交回复
热议问题