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

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

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

Ответ 1

Я думаю, что чтение 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

Ответ 2

Если вы используете стандартную защиту 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, который охватывает все базы на сервере, а не клиент.

Ответ 3

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

# Gemfile
gem 'angular_rails_csrf'

Ответ 4

Ответ, который объединяет все предыдущие ответы, и полагается, что вы используете 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')

Ответ 5

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

Я использовал контент из ответа 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

Ответ 7

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

а. На мой взгляд, я инициализирую переменную $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 = {}

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

Ответ 8

 angular
  .module('corsInterceptor', ['ngCookies'])
  .factory(
    'corsInterceptor',
    function ($cookies) {
      return {
        request: function(config) {
          config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
          return config;
        }
      };
    }
  );

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