我有一个简单的 Ruby on Rails 表单,其中包含一个authenticity_token。不幸的是,我错过了当您页面缓存此页面时,Authenticity Token 变得无效。不过,我很高兴我弄明白了。
在这种情况下如何解决缓存问题?
我有一个简单的 Ruby on Rails 表单,其中包含一个authenticity_token。不幸的是,我错过了当您页面缓存此页面时,Authenticity Token 变得无效。不过,我很高兴我弄明白了。
在这种情况下如何解决缓存问题?
正如 Matchu 发布的那样,您可以从这篇文章中实现第二点(他发布的链接相同,但也可以通过我的谷歌搜索找到)。这增加了对 JavaScript 的依赖,这可能是也可能不是您想要的。
或者,您可以查看Fragment Caching。这允许您缓存页面的某些部分,但仍会生成动态部分(例如带有真实性令牌的表单)。使用这种技术,您可以缓存页面的其余部分,但为每个请求生成一个新表单。
一个最终解决方案(但最不受欢迎的)是禁用该特定操作的真实性令牌。您可以通过将以下内容添加到生成该表单的控制器的开头来执行此操作:
protect_from_forgery :except => [:your_action]
您还可以通过在开头添加以下内容来关闭整个控制器的protect_from_forgery:
skip_before_filter :verify_authenticity_token
这似乎不是一个很好解决的问题。这篇博文的第二点描述了如何使用 jQuery 完成任务,但这会引入 Javascript 依赖项。权衡你的选择,我想。
您可以在缓存标记中呈现自定义标记,并将其替换为每次请求时呈现的表单。
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,并且会稍微降低缓存带来的性能增益。我认为有人实现了与机架中间件类似的模式。
我遵循 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
作为更通用的解决方案,您还可以将所有缓存的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
的 想法。