私は、authenticity_token を含む単純な Ruby on Rails フォームを持っています。残念ながら、このページをページ キャッシュすると認証トークンが無効になることを見逃していました。しかし、私はそれを理解できてうれしいです。
このような場合、どのようにキャッシングを解決しますか?
私は、authenticity_token を含む単純な Ruby on Rails フォームを持っています。残念ながら、このページをページ キャッシュすると認証トークンが無効になることを見逃していました。しかし、私はそれを理解できてうれしいです。
このような場合、どのようにキャッシングを解決しますか?
Matchu が投稿したように、この投稿からポイント 2 を実装できます(彼が投稿したのと同じリンクですが、私のグーグル経由でも見つかりました)。これにより、JavaScript への依存関係が追加されます。これは、必要なものである場合とそうでない場合があります。
または、 Fragment Cachingを調べることもできます。これにより、ページの特定の部分をキャッシュできますが、動的な部分 (認証トークンを含むフォームなど) を生成することもできます。この手法を使用すると、ページの残りの部分をキャッシュできますが、リクエストごとに新しいフォームを生成できます。
最終的な解決策の 1 つ (ただし、最も好ましくない) は、その特定のアクションの認証トークンを無効にすることです。これを行うには、そのフォームを生成するコントローラーの先頭に次を追加します。
protect_from_forgery :except => [:your_action]
以下を先頭に追加することで、コントローラー全体の protect_from_forgery をオフにすることもできます。
skip_before_filter :verify_authenticity_token
キャッシュされたマークアップでカスタム タグをレンダリングし、すべてのリクエストでレンダリングされるフォームに置き換えることができます。
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 %>
はい、これはハックと見なすことができますが、偽造防止を緩めることはなく、JS を必要とせず、キャッシュによるパフォーマンスの向上を少しだけ低下させます。誰かが Rack Middleware と同様のパターンを実装したと思います。
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
それはうまく解決された問題ではないようです。このブログ投稿のポイント 2 では、jQuery を使用してタスクを実行する方法について説明していますが、これには Javascript の依存関係が含まれています。あなたのオプションを比較検討してください。
より一般的な解決策として、キャッシュされたすべての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 に敬意を表します。