Ruby on Rails形成页面缓存,包括authenticity_token

时间:2010-03-05 04:30:19

标签: ruby-on-rails forms caching authenticity-token

我有一个简单的Ruby on Rails表单,其中包含authenticity_token。不幸的是,我错过了当您页面缓存此页面时,真实性令牌变为无效。我很高兴我明白了。

在这种情况下如何解决缓存?

5 个答案:

答案 0 :(得分:23)

正如Matchu发布的那样,你可以从this post实现第二点(他发布了同样的链接,但也通过我的谷歌搜索找到了)。这增加了对JavaScript的依赖,这可能是您想要的,也可能不是。

或者,您可以查看Fragment Caching。这允许您缓存页面的某些部分,但仍然生成动态部分(例如具有真实性令牌的表单)。使用此技术,您可以缓存页面的其余部分,但为每个请求生成一个新表单。

最终解决方案(但最不利的)是禁用该特定操作的真实性标记。您可以通过将以下内容添加到生成该表单的控制器的开头来执行此操作:

protect_from_forgery :except => [:your_action]

您还可以通过在开头添加以下内容来关闭整个控制器的protect_from_forgery:

skip_before_filter :verify_authenticity_token

答案 1 :(得分:1)

这似乎不是一个解决得很好的问题。 Point two on this blog post描述了如何使用jQuery完成任务,但这引入了Javascript依赖。我想,权衡你的选择。

答案 2 :(得分:1)

您可以在缓存标记中呈现自定义标记,并将其替换为每次请求时呈现的表单。

module CacheHelper
  # Our FORM is deeply nested in the CACHED_PARTIAl, which we
  # cache. It must be rendered on every request because of its
  # authenticity_token by protect_from_forgery. Instead of splitting up the
  # cache in multiple fragments, we replace a special tag with the custom
  # form.
  def cache_with_bla_form(resource, &block)
    form = nil
    doc = Nokogiri::HTML::DocumentFragment.parse( capture { cache("your_cache_key",&block) } )
    doc.css('uncachable_form').each do |element|
      form ||= render(:partial => 'uncachable_form', :resource => resource)
      element.replace form
    end
    doc.to_html
  end
end

在您的视图中,您只需呈现一个空的uncachable_form标记。

<%- cache_with_bla_form resource do %>
  # cachable stuff..
  <uncachable_form />
  # more cachable stuff
<%- end %>

是的,这可以被认为是一个Hack,但它不会放松伪造保护,不需要JS,并且可以通过缓存来降低性能。我认为有人实现了与Rack Middleware类似的模式。

答案 3 :(得分:1)

我遵循了Niklas Hofer的一般解决方案,但我发现他的实现与Rails缓存助手的确切语义不匹配。也就是说,它试图从帮助程序返回缓存的HTML,而不是使用safe_concat将其写入缓冲区,这是Rails帮助程序所做的。

Rails帮助程序用法如下:

- cache do
  = something

他的解决方案需要这种语法:

= cache_with_updated_csrf do
  = something

为了保持一致性,我希望这些工作方式相同。因此我使用了这种语法:

- cache_form do
  = something

这是我的实施。当禁用缓存时,它也会跳过缓存,就像Rails帮助程序一样。

module CacheHelper
  # Cache a form with a fresh CSRF
  def cache_form(name = {}, options = nil, &block)
    if controller.perform_caching
      fragment = fragment_for(name, options, &block)

      fragment_with_fresh_csrf = Nokogiri::HTML::DocumentFragment.parse( fragment ).tap do |doc|
        doc.css("input[name=#{request_forgery_protection_token}]").each { |e| e['value'] = form_authenticity_token }
      end.to_html

      safe_concat fragment_with_fresh_csrf
    else
      yield
    end

    nil
  end
end

答案 4 :(得分:0)

作为一种更通用的解决方案,您还可以将所有缓存的authenticity_tokens替换为当前的缓存:

module CacheHelper
  def cache_with_updated_csrf(*a, &block)
    Nokogiri::HTML::DocumentFragment.parse( capture { cache(*a,&block) } ).tap do |doc|
      doc.css("input[name=#{request_forgery_protection_token}]").each { |e| e['value'] = form_authenticity_token }
    end.to_html.html_safe
  end
end

在您的观看次数中使用= cache_with_updated_csrf do代替- cache do。感谢Bernard Potocki的想法。