wake-up-neo.com

Rails CSRF Protection + Angular.js: Mit protect_from_forgery kann ich mich abmelden POST

Wenn der protect_from_forgery Option wird in application_controller erwähnt, dann kann ich mich anmelden und alle GET-Anfragen ausführen, aber bei allerersten POST request Rails setzt die Sitzung zurück, die sich anmeldet mich raus.

Ich drehte den protect_from_forgery Option vorübergehend deaktiviert, möchte sie jedoch mit Angular.js verwenden. Gibt es eine Möglichkeit, das zu tun?

126
Paul

Ich denke, dass das Lesen des CSRF-Werts von DOM keine gute Lösung ist, sondern nur eine Problemumgehung.

Hier ist ein Dokument aus der offiziellen AngularJS-Website http://docs.angularjs.org/api/ng.$http :

Da nur JavaScript, das auf Ihrer Domain ausgeführt wird, das Cookie lesen konnte, kann Ihr Server sicher sein, dass die XHR von JavaScript stammt, das auf Ihrer Domain ausgeführt wird.

Um dies nutzen zu können (CSRF-Schutz), muss Ihr Server bei der ersten HTTP-GET-Anforderung ein Token in einem von JavaScript lesbaren Sitzungscookie namens XSRF-TOKEN festlegen. Bei nachfolgenden Nicht-GET-Anforderungen kann der Server überprüfen, ob das Cookie mit dem X-XSRF-TOKEN-HTTP-Header übereinstimmt

Hier ist meine Lösung basierend auf diesen Anweisungen:

Setzen Sie zuerst den Cookie:

# app/controllers/application_controller.rb

# Turn on request forgery protection
protect_from_forgery

after_action :set_csrf_cookie

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

Dann sollten wir das Token bei jeder Nicht-GET-Anforderung überprüfen.
Da Rails bereits mit der ähnlichen Methode erstellt wurde, können wir sie einfach überschreiben, um unsere Logik anzufügen:

# 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
275
HungYuHei

Wenn Sie die Standardeinstellung Rails CSRF-Schutz (<%= csrf_meta_tags %>) können Sie Ihr Angular Modul wie folgt konfigurieren:

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

Oder wenn Sie kein CoffeeScript verwenden (was?):

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

Wenn Sie es vorziehen, können Sie den Header nur bei Nicht-GET-Anforderungen mit etwa den folgenden Angaben senden:

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
]

Lesen Sie auch HungYuHeis Antwort , das alle Basen auf dem Server und nicht auf dem Client abdeckt.

78
Michelle Tilley

Der Edelstein angle_Rails_csrf fügt allen Ihren Controllern automatisch Unterstützung für das in HungYuHeis Antwort beschriebene Muster hinzu:

# Gemfile
gem 'angular_Rails_csrf'
29
jsanders

Die Antwort, bei der alle vorherigen Antworten zusammengeführt werden und die davon abhängt, dass Sie den Authentifizierungs-Gem Devise verwenden.

Fügen Sie zunächst den Edelstein hinzu:

gem 'angular_Rails_csrf'

Fügen Sie als Nächstes den Block rescue_from In application_controller.rb hinzu:

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

Und zum Schluss fügen Sie das Interceptor-Modul hinzu angular App.

# 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')
4
Anton Orel

Ich sah die anderen Antworten und fand sie großartig und gut durchdacht. Ich habe meine Rails - App zwar mit einer meiner Meinung nach einfacheren Lösung zum Laufen gebracht, also dachte ich, ich würde sie teilen. In meiner Rails App war dies standardmäßig enthalten.

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

Ich habe die Kommentare gelesen und es schien so, als ob ich angular verwenden und den csrf-Fehler vermeiden möchte. Ich habe es in dieses geändert,

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

Und jetzt funktioniert es! Ich sehe keinen Grund, warum dies nicht funktionieren sollte, aber ich würde gerne Einblicke von anderen Postern erhalten.

1
Blaine Hatab

Ich habe einen sehr schnellen Hack gefunden. Ich musste nur Folgendes tun:

ein. Meiner Ansicht nach initialisiere ich ein $scope Variable, die das Token enthält, sagen wir vor dem Formular oder noch besser bei der Controller-Initialisierung:

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

b. Bevor ich meinen neuen Eintrag in meinem AngularJS-Controller speichere, füge ich den Token dem Hash hinzu:

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

Es muss nichts mehr getan werden.

1
Ruby Racer

Ich habe den Inhalt aus der Antwort von HungYuHei in meiner Bewerbung verwendet. Ich stellte jedoch fest, dass ich einige zusätzliche Probleme hatte, einige wegen der Verwendung von Devise für die Authentifizierung und andere wegen der Standardeinstellung, die ich mit meiner Anwendung hatte:

protect_from_forgery with: :exception

Ich nehme das verwandte Stapelüberlauf-Frage und die Antworten dort zur Kenntnis und schrieb ein viel ausführlicheres Blog-Beitrag , das die verschiedenen Überlegungen zusammenfasst. Die Teile dieser Lösung, die hier relevant sind, sind im Anwendungscontroller:

  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
PaulL
 angular
  .module('corsInterceptor', ['ngCookies'])
  .factory(
    'corsInterceptor',
    function ($cookies) {
      return {
        request: function(config) {
          config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
          return config;
        }
      };
    }
  );

Es funktioniert auf der eckigen Seite!

0