18

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

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

4

5 回答 5

24

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

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

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

protect_from_forgery :except => [:your_action]

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

skip_before_filter :verify_authenticity_token
于 2010-03-15T17:35:36.690 回答
1

这似乎不是一个很好解决的问题。这篇博文的第二点描述了如何使用 jQuery 完成任务,但这会引入 Javascript 依赖项。权衡你的选择,我想。

于 2010-03-05T04:37:56.633 回答
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,并且会稍微降低缓存带来的性能增益。我认为有人实现了与机架中间件类似的模式。

于 2012-07-25T14:20:25.053 回答
1

我遵循 Niklas Hofer 的通用解决方案,但我发现他的实现与 Rails 缓存助手的确切语义不匹配。safe_concat也就是说,它试图从帮助程序返回缓存的 HTML,而不是使用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
于 2013-05-22T20:57:48.613 回答
0

作为更通用的解决方案,您还可以将所有缓存的authentity_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而不是。感谢Bernard Potocki- cache do的 想法。

于 2012-07-26T12:31:56.343 回答