Rails защищает от подделки с помощью Javascript

Я запутался в странный CSRF, где я пытаюсь получить доступ к файлу javascript, загруженному на моем сервере rails. У меня есть контроллер, например:

class SomeController < ApplicationController
  def show
    some_path = "/some/js/file/on/disk.js"
    send_file(some_path, type: "text/javascript", disposition: :inline)
  end
end

Однако при переходе на http://localhost:3000/somes/1 появляется сообщение об ошибке:

Предупреждение о безопасности: встроенный тэг на другом запрошенном сайте защищенный JavaScript. Если вы знаете, что делаете, идите вперед и отключите защиту от подделок в этом действии, чтобы разрешить перекрестное происхождение Встраивание JavaScript.

Извлеченный источник (вокруг строки # 225):

    if marked_for_same_origin_verification? && non_xhr_javascript_response?
      logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING if logger
      raise ActionController::InvalidCrossOriginRequest, CROSS_ORIGIN_JAVASCRIPT_WARNING
    end
  end

Обратите внимание, что я обращаюсь к этой странице напрямую, а это значит, что нет макета, поэтому я не могу включить токен CSRF в свой макет.

Есть ли что-то, что нужно сделать по-другому, чтобы правильно получить доступ к этому ресурсу?

РЕДАКТИРОВАТЬ: Для запроса комментариев я добавил полный трассировку ниже.

actionpack (4.2.6) Библиотека/action_controller/металл/request_forgery_protection.rb: 225: в verify_same_origin_request' activesupport (4.2.6) lib/active_support/callbacks.rb:432:in блок в make_lambda ' activesupport (4.2.6) lib/active_support/callbacks.rb: 239: in block in halting' activesupport (4.2.6) lib/active_support/callbacks.rb:506:in block in call 'activesupport (4.2.6) lib/active_support/callbacks.rb: 506: in each' activesupport (4.2.6) lib/active_support/callbacks.rb:506:in call 'activesupport (4.2.6) lib/active_support/callbacks.rb: 92: in __run_callbacks__' activesupport (4.2.6) lib/active_support/callbacks.rb:778:in _run_process_action_callbacks 'activesupport (4.2.6) lib/active_support/callbacks.rb: 81: in run_callbacks' actionpack (4.2.6) lib/abstract_controller/callbacks.rb:19:in process_action ' actionpack (4.2.6) lib/action_controller/metal/rescue.rb: 29: in process_action' actionpack (4.2.6) lib/action_controller/metal/instrumentation.rb:32:in заблокировать process_action 'activesupport (4.2.6) lib/active_support/notifications.rb: 164: in block in instrument' activesupport (4.2.6) lib/active_support/notifications/instrumenter.rb:20:in instrument ' activesupport (4.2.6) lib/active_support/notifications.rb: 164: in instrument' actionpack (4.2.6) lib/action_controller/metal/instrumentation.rb:30:in process_action "actionpack (4.2.6) lib/action_controller/metal/params_wrapper.rb: 250: in process_action' activerecord (4.2.6) lib/active_record/railties/controller_runtime.rb:18:in process_action 'actionpack (4.2.6) lib/abstract_controller/base.rb: 137: in process' actionview (4.2.6) lib/action_view/rendering.rb:30:in process 'actionpack (4.2.6) lib/action_controller/metal.rb: 196: in dispatch' actionpack (4.2.6) lib/action_controller/metal/rack_delegation.rb:13:in отправка ' actionpack (4.2.6) lib/action_controller/metal.rb: 237: in block in action' actionpack (4.2.6) lib/action_dispatch/routing/route_set.rb:74:in отправка" actionpack (4.2.6) lib/action_dispatch/routing/route_set.rb: 43: in serve' actionpack (4.2.6) lib/action_dispatch/journey/router.rb:43:in block в сервисный пакет (4.2.6) lib/action_dispatch/travel/router.rb: 30: in each' actionpack (4.2.6) lib/action_dispatch/journey/router.rb:30:in serve 'actionpack (4.2.6) lib/action_dispatch/routing/route_set.rb: 817: in call' bullet (5.1.1) lib/bullet/rack.rb:12:in call 'warden (1.2.6) lib/warden/manager.rb: 35: in block in call' warden (1.2.6) lib/warden/manager.rb:34:in catch 'warden (1.2.6) lib/warden/manager.rb: 34: in call' rack (1.6.4) lib/rack/etag.rb:24:in call 'rack (1.6.4) lib/rack/conditionalget.rb: 25: in call' rack (1.6.4) lib/rack/head.rb:13:in call' actionpack (4.2.6) lib/action_dispatch/middleware/params_parser.rb: 27: in call' actionpack (4.2.6) lib/action_dispatch/middleware/flash.rb:260:in call 'rack (1.6.4) lib/rack/session/abstract/id.rb: 225: in context' rack (1.6.4) lib/rack/session/abstract/id.rb:220:in call' actionpack (4.2.6) lib/action_dispatch/middleware/cookies.rb: 560: in call' activerecord (4.2.6) lib/active_record/query_cache.rb:36:in call ' activerecord (4.2.6) Библиотека/active_record/connection_adapters/аннотация/connection_pool.rb: 653: в call' activerecord (4.2.6) lib/active_record/migration.rb:377:in call 'actionpack (4.2.6) lib/action_dispatch/middleware/callbacks.rb: 29: in block in call' activesupport (4.2.6) lib/active_support/callbacks.rb:88:in run_callbacks 'activesupport (4.2.6) lib/active_support/callbacks.rb: 778: in _run_call_callbacks' activesupport (4.2.6) lib/active_support/callbacks.rb:81:in run_callbacks 'actionpack (4.2.6) lib/action_dispatch/middleware/callbacks.rb: 27: in call' actionpack (4.2.6) lib/action_dispatch/middleware/reloader.rb:73:in call ' actionpack (4.2.6) lib/action_dispatch/middleware/remote_ip.rb: 78: in call' actionpack (4.2.6) lib/action_dispatch/middleware/debug_exceptions.rb:17:in Вызов" web-консоль (2.3.0) lib/web_console/middleware.rb: 28: in block in call' web-console (2.3.0) lib/web_console/middleware.rb:18:in catch ' web-консоль (2.3.0) lib/web_console/middleware.rb: 18: in call' actionpack (4.2.6) lib/action_dispatch/middleware/show_exceptions.rb:30:in call ' railties (4.2.6) lib/rails/rack/logger.rb: 38: in call_app' railties (4.2.6) lib/rails/rack/logger.rb:20:in block in call 'activesupport (4.2.6) lib/active_support/tagged_logging.rb: 68: in block in tagged' activesupport (4.2.6) lib/active_support/tagged_logging.rb:26:in tagged 'activesupport (4.2.6) lib/active_support/tagged_logging.rb: 68: in tagged' railties (4.2.6) lib/rails/rack/logger.rb:20:in вызов 'quiet_assets (1.1.0) lib/quiet_assets.rb: 27: in call_with_quiet_assets' request_store (1.3.1) lib/request_store/middleware.rb:9:in call 'actionpack (4.2.6) lib/action_dispatch/middleware/request_id.rb: 21: in call' rack (1.6.4) lib/rack/methodoverride.rb:22:in call 'rack (1.6.4) lib/rack/runtime.rb: 18: in call' activesupport (4.2.6) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in call 'rack (1.6.4) lib/rack/lock.rb: 17: in call' actionpack (4.2.6) lib/action_dispatch/middleware/static.rb:120:in call' rack (1.6.4) lib/rack/sendfile.rb: 113: in call' railties (4.2.6) lib/rails/engine.rb:518:in call 'railties (4.2.6) lib/rails/application.rb: 165: in call' rack (1.6.4) lib/rack/content_length.rb:15:in вызов 'puma (3.5.0) lib/puma/configuration.rb: 225: in call' puma (3.5.0) lib/puma/server.rb:569:in handle_request 'puma (3.5.0) lib/puma/server.rb: 406: in process_client' puma (3.5.0) lib/puma/server.rb:271:in block in run 'puma (3.5.0) lib/puma/thread_pool.rb: 116: в `block in spawn_thread '

Ответ 1

Некоторые предложения:

1) Не забудьте добавить <%= csrf_meta_tag %> в макет

2) Убедитесь, что вы включили скрытое поле csrf-token. Например, если вы используете форму в представлении show. Обычно разработчики форм делают это автоматически.

3) Установите application/javascript" в файле send_file

if request.format.js?
   send_file(assetfilename, type: 'application/javascript')
else
   send_file(assetfilename)
end

Ответ 2

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

class SomeController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :show

  def show
    some_path = "/some/js/file/on/disk.js"
    send_file(some_path, type: "text/javascript", disposition: :inline)
  end
end

Ответ 3

Проверка условий, при которых возникает ошибка:

marked_for_same_origin_verification? && non_xhr_javascript_response?

Я пошел в и нашел:

  # GET requests are checked for cross-origin JavaScript after rendering.
  def mark_for_same_origin_verification!
    @marked_for_same_origin_verification = request.get?
  end

  # If the `verify_authenticity_token` before_action ran, verify that
  # JavaScript responses are only served to same-origin GET requests.
  def marked_for_same_origin_verification?
    @marked_for_same_origin_verification ||= false
  end

Итак, это похоже на истину, если это запрос GET.

В то же время,

  def non_xhr_javascript_response?
    content_type =~ %r(\Atext/javascript) && !request.xhr?
  end

Что, как представляется, описывает ваш ответ - запрос без XHR, приводящий файл с текстом /javascript.

Я предполагаю, что вы могли бы избежать этого (кроме пропущенных verify_authenticity_token), разветкив ветвь рельсов и изменив одно из этих условий, или внедрив некоторый макет, чтобы ответ был не просто javascript.

Ответ 4

Я не буду спрашивать, почему вы используете контроллер для отправки javascript файла в браузер, хотя это не кажется хорошей идеей. Надеюсь, эти предложения помогут.

Вы можете попробовать

class SomeController < ApplicationController
  def show
    some_path = "/some/js/file/on/disk.js"

    respond_to do |format|
      format.js {
        send_file(some_path, type: "text/javascript", disposition: :inline) 
      }
      format.html {
        "Html request from browser. Try sending a js request to get <Javascript>"
      }
    end
  end
end

Другой ответ - изменить обработку CSRF. Это похоже на ответ, который предложил Михал,

    class SomeController < ApplicationController
        protect_from_forgery except: :show 
        ...
    end

По моему мнению, изменение подхода к управлению CSRF значительно шире. Отключение CSRF для данного метода в контроллере предоставляет то, что вам может не понравиться.


Вот несколько дополнительных предложений.

Он может быть старомодным, но curl позволяет получить полный контроль над заголовками HTTP-запросов, а также увидеть полный HTTP-запрос ответ. Вызвав curl -H "Content-Type: application/javascript" http://someurl/here/1, вы сможете точно увидеть, что происходит и почему ваш браузер не может обслуживать запрошенный файл javascript, или если есть обходной путь.

Наконец, если вы пытаетесь обслуживать статические (javascript) файлы в Rails, существует много дополнительных накладных и потенциальных рисков безопасности, использующих контроллер для выполнения этого действия. Если для использования контроллера не существует веских оснований, более простым решением было бы хранить файлы в подкаталоге каталога. /public на сервере, чтобы каждый и каждый могли читать файл (ы). Когда вы развертываете приложение в производственной среде, это может сэкономить еще больше накладных расходов, но это выходит за рамки вашего первоначального вопроса.

Удачи!