Кэширование страниц формы Ruby on Rails, включая authenticity_token

У меня есть простая форма Ruby on Rails, которая включает в себя authenticity_token. К сожалению, я пропустил, что когда вы кешируете страницу, то токен аутентификации становится недействительным. Я рад, что понял это как бы то ни было.

Как вы решаете кеширование в таком случае?

5 ответов

Решение

Как писал Matchu, вы можете реализовать пункт два из этого поста (ту же самую ссылку, которую он разместил, но также нашел через мой Google). Это добавляет зависимость от JavaScript, который может или не может быть то, что вы хотите.

В качестве альтернативы, вы можете посмотреть на Фрагмент Кэширования. Это позволяет вам кэшировать определенные части страницы, но при этом генерировать динамические части (например, формы с токенами подлинности). Используя эту технику, вы можете кэшировать оставшуюся часть страницы, но генерировать новую форму для каждого запроса.

Одно окончательное решение (но наименее благоприятное) - отключить токен аутентификации для этого конкретного действия. Вы можете сделать это, добавив следующее в начало контроллера, генерирующего эту форму:

protect_from_forgery :except => [:your_action]

Вы также можете отключить protect_from_forgery для всего контроллера, добавив в начало следующее:

skip_before_filter :verify_authenticity_token

Это не кажется хорошо решенной проблемой. Второй пункт в этом посте блога описывает, как выполнить задачу с помощью jQuery, но это вводит зависимость Javascript. Взвесьте ваши варианты, я полагаю.

Я следовал общему решению Никласа Хофера, но обнаружил, что его реализация не соответствует точной семантике хелпера 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

Вы можете отобразить пользовательский тег в разметке кэша и заменить его формой, отображаемой при каждом запросе.

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.

В качестве более общего решения вы также можете заменить все кэшированные 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 за идею.

Другие вопросы по тегам