Rails CSRF Protection + Angular.js: protect_from_forgery заставляет меня выйти из системы POST

120

Если параметр protect_from_forgery указан в application_controller, то я могу входить в систему и выполнять любые запросы GET, но при первом запросе POST Rails сбрасывает сеанс, который выводит меня из системы.

Я временно отключил параметр protect_from_forgery, но хотел бы использовать его с Angular.js. Есть ли способ сделать это?

  • 0
    Посмотрите, поможет ли это кому-нибудь, речь идет об установке заголовков HTTP stackoverflow.com/questions/14183025/…
Теги:
csrf

8 ответов

277
Лучший ответ

Я думаю, что чтение CSRF-значения из DOM не является хорошим решением, это просто обходной путь.

Вот форма документа angularJS официальный сайт http://docs.angularjs.org/api/ng.$http:

Поскольку только JavaScript, который работает в вашем домене, может читать cookie, ваш сервер может быть уверен, что XHR получен из JavaScript, запущенного в вашем домене.

Чтобы воспользоваться этим (CSRF Protection), вашему серверу необходимо установить токен в читаемом сеансе JavaScript cookie под названием XSRF-TOKEN при первом запросе HTTP GET. На последующих не-GET-запросы сервер может проверить соответствие файлов cookie HTTP-заголовок X-XSRF-TOKEN

Вот мое решение, основанное на этих инструкциях:

Сначала установите cookie:

# app/controllers/application_controller.rb

# Turn on request forgery protection
protect_from_forgery

after_filter :set_csrf_cookie_for_ng

def set_csrf_cookie_for_ng
  cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end

Затем мы должны проверить токен на каждом запросе, отличном от GET.
Поскольку Rails уже построена с помощью аналогичного метода, мы можем просто просто переопределить его для добавления нашей логики:

# app/controllers/application_controller.rb

protected

  # In Rails 4.2 and above
  def verified_request?
    super || valid_authenticity_token?(session, request.headers['X-XSRF-TOKEN'])
  end

  # In Rails 4.1 and below
  def verified_request?
    super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  end
  • 18
    Мне нравится эта техника, так как вам не нужно изменять какой-либо клиентский код.
  • 2
    Спасибо. Мне это нравится. Стоит отметить, что для того, чтобы этот код работал, должен присутствовать protect_from_forgery.
Показать ещё 18 комментариев
81

Если вы используете стандартную защиту Rails CSRF (<%= csrf_meta_tags %>), вы можете настроить свой модуль Angular следующим образом:

myAngularApp.config ["$httpProvider", ($httpProvider) ->
  $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
]

Или, если вы не используете CoffeeScript (что!?):

myAngularApp.config([
  "$httpProvider", function($httpProvider) {
    $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
  }
]);

Если вы предпочитаете, вы можете отправлять заголовок только по запросам, отличным от GET, с чем-то вроде следующего:

myAngularApp.config ["$httpProvider", ($httpProvider) ->
  csrfToken = $('meta[name=csrf-token]').attr('content')
  $httpProvider.defaults.headers.post['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.put['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.patch['X-CSRF-Token'] = csrfToken
  $httpProvider.defaults.headers.delete['X-CSRF-Token'] = csrfToken
]

Кроме того, не забудьте проверить ответ HungYuHei, который охватывает все базы на сервере, а не клиент.

  • 0
    Позволь мне объяснить. Базовый документ представляет собой обычный HTML, а не .erb, поэтому я не могу использовать <%= csrf_meta_tags %> . Я думал, что должно быть достаточно, чтобы упомянуть только protect_from_forgery . Что делать? Базовый документ должен быть простым HTML (я здесь не тот, кто выбирает).
  • 3
    Когда вы используете protect_from_forgery вы говорите: «когда мой код JavaScript отправляет запросы Ajax, я обещаю отправить X-CSRF-Token токен в заголовке, который соответствует текущему токену CSRF». Чтобы получить этот токен, Rails внедряет его в DOM с помощью <%= csrf_meta_token %> и get получает содержимое метатега с помощью jQuery всякий раз, когда он делает запросы Ajax (драйвер UJS по умолчанию для Rails 3 делает это за вас). Если вы не используете ERB, нет способа получить текущий токен из Rails на страницу и / или в JavaScript - и, таким образом, вы не можете использовать protect_from_forgery таким образом.
Показать ещё 9 комментариев
29

angular_rails_csrf gem автоматически добавляет поддержку шаблона, описанного в ответ HungYuHei всем вашим контроллерам:

# Gemfile
gem 'angular_rails_csrf'
  • 0
    Любая идея, как вы должны настроить контроллер приложения и другие параметры, связанные с csrf / forgery, чтобы правильно использовать angular_rails_csrf?
  • 0
    И вы должны перезагрузить сервер после комплектации. Может быть, очевидно ...
Показать ещё 2 комментария
3

Ответ, который объединяет все предыдущие ответы, и полагается, что вы используете Devise подлинник gem.

Прежде всего, добавьте драгоценный камень:

gem 'angular_rails_csrf'

Затем добавьте блок rescue_from в application_controller.rb:

protect_from_forgery with: :exception

rescue_from ActionController::InvalidAuthenticityToken do |exception|
  cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  render text: 'Invalid authenticity token', status: :unprocessable_entity
end

И, наконец, добавьте модуль перехватчика к вам angular.

# coffee script
app.factory 'csrfInterceptor', ['$q', '$injector', ($q, $injector) ->
  responseError: (rejection) ->
    if rejection.status == 422 && rejection.data == 'Invalid authenticity token'
        deferred = $q.defer()

        successCallback = (resp) ->
          deferred.resolve(resp)
        errorCallback = (resp) ->
          deferred.reject(resp)

        $http = $http || $injector.get('$http')
        $http(rejection.config).then(successCallback, errorCallback)
        return deferred.promise

    $q.reject(rejection)
]

app.config ($httpProvider) ->
  $httpProvider.interceptors.unshift('csrfInterceptor')
  • 1
    Почему вы вводите $injector вместо того, чтобы просто вводить $http ?
  • 0
    Это работает, но только думаю, что я добавил, это проверить, если запрос уже повторен. Когда это повторяется, мы не отправляем снова, так как он будет повторяться вечно.
1

Я очень быстро взломал это. Все, что мне нужно было сделать, это следующее:

а. На мой взгляд, я инициализирую переменную $scope, которая содержит токен, скажем, перед формой или даже лучше при инициализации контроллера:

<div ng-controller="MyCtrl" ng-init="authenticity_token = '<%= form_authenticity_token %>'">

б. В моем контроллере AngularJS перед сохранением новой записи я добавляю токен в хэш:

$scope.addEntry = ->
    $scope.newEntry.authenticity_token = $scope.authenticity_token 
    entry = Entry.save($scope.newEntry)
    $scope.entries.push(entry)
    $scope.newEntry = {}

Больше ничего не нужно делать.

1

Я использовал контент из ответа HungYuHei в своем приложении. Я обнаружил, что я имел дело с несколькими дополнительными проблемами, однако, некоторые из-за моего использования Devise для аутентификации, а некоторые из-за того, что по умолчанию я получил свое приложение:

protect_from_forgery with: :exception

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

  protect_from_forgery with: :exception

  after_filter :set_csrf_cookie_for_ng

  def set_csrf_cookie_for_ng
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
  end

  rescue_from ActionController::InvalidAuthenticityToken do |exception|
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
    render :error => 'Invalid authenticity token', {:status => :unprocessable_entity} 
  end

protected
  def verified_request?
    super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
  end
1

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

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
end

Я прочитал комментарии, и мне показалось, что я хочу использовать angular и избежать ошибки csrf. Я изменил его на это,

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :null_session
end

И теперь это работает! Я не вижу причин, почему это не должно работать, но я хотел бы услышать некоторое понимание других плакатов.

  • 6
    это вызовет проблемы, если вы попытаетесь использовать rails 'session', поскольку для него будет установлено значение nil, если он не пройдёт тест на подделку, что будет всегда, так как вы не отправляете токен csrf со стороны клиента.
  • 0
    Спасибо за понимание. Действительно полезно знать.
Показать ещё 1 комментарий
0
 angular
  .module('corsInterceptor', ['ngCookies'])
  .factory(
    'corsInterceptor',
    function ($cookies) {
      return {
        request: function(config) {
          config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
          return config;
        }
      };
    }
  );

Работает с угловой стороной!

Ещё вопросы

Сообщество Overcoder
Наверх
Меню