我有一个简单的Ruby on Rails表单,其中包含authenticity_token。不幸的是,我错过了当您页面缓存此页面时,真实性令牌变为无效。我很高兴我明白了。
在这种情况下如何解决缓存?
答案 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的想法。