Jeśli protect_from_forgery
opcja jest wymieniona w application_controller, mogę się zalogować i wykonać dowolne żądanie GET, ale przy pierwszym żądaniu POST Railsy resetują sesję, co powoduje wylogowanie.
protect_from_forgery
Tymczasowo wyłączyłem tę opcję, ale chciałbym jej używać z Angular.js. Czy jest jakiś sposób, aby to zrobić?
Odpowiedzi:
Myślę, że odczytywanie wartości CSRF z DOM nie jest dobrym rozwiązaniem, to tylko obejście.
Oto dokument w formie oficjalnej strony angularJS http://docs.angularjs.org/api/ng.$http :
Oto moje rozwiązanie oparte na tych instrukcjach:
Najpierw ustaw plik 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
Następnie powinniśmy zweryfikować token przy każdym żądaniu innym niż GET.
Ponieważ Railsy zbudowały już podobną metodę, możemy po prostu nadpisać ją i dołączyć naszą logikę:
# 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
źródło
Jeśli używasz domyślnej ochrony Rails CSRF (
<%= csrf_meta_tags %>
), możesz skonfigurować swój moduł Angular w następujący sposób:myAngularApp.config ["$httpProvider", ($httpProvider) -> $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content') ]
Lub, jeśli nie używasz CoffeeScript (co !?):
myAngularApp.config([ "$httpProvider", function($httpProvider) { $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content'); } ]);
Jeśli wolisz, możesz wysłać nagłówek tylko w przypadku żądań innych niż GET z czymś takim jak:
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 ]
Pamiętaj również, aby sprawdzić odpowiedź HungYuHei , która obejmuje wszystkie bazy na serwerze, a nie na kliencie.
źródło
<%= csrf_meta_tags %>
. Pomyślałem, że wystarczyprotect_from_forgery
tylko wspomnieć . Co robić? Podstawowym dokumentem musi być zwykły HTML (nie jestem tym, który wybiera).protect_from_forgery
tego, co mówisz, to „kiedy mój kod JavaScript wysyła żądania Ajax, obiecuję wysłaćX-CSRF-Token
w nagłówku symbol odpowiadający aktualnemu tokenowi CSRF”. Aby uzyskać ten token, Railsy wstrzykują go do DOM za pomocą<%= csrf_meta_token %>
i pobierają zawartość metatagu za pomocą jQuery za każdym razem, gdy wysyła żądania Ajax (domyślny sterownik UJS Rails 3 robi to za Ciebie). Jeśli nie używasz ERB, nie ma sposobu, aby pobrać aktualny token z Railsów na stronę i / lub JavaScript - i dlatego nie możesz używaćprotect_from_forgery
w ten sposób.csrf_meta_tags
każdym razem, gdy serwer generuje odpowiedź, i za każdym razem te tagi różnią się od poprzednich. Zatem te tagi są unikalne dla każdego żądania. Pytanie brzmi: w jaki sposób aplikacja otrzymuje te tagi dla żądania AJAX (bez kąta)? Użyłem protect_from_forgery z żądaniami jQuery POST, nigdy nie zawracałem sobie głowy uzyskaniem tego tokena CSRF i zadziałało. W jaki sposób?jQuery.ajaxPrefilter
jak pokazano tutaj: github.com/indirect/jquery-rails/blob/c1eb6ae/vendor/assets/ ... Możesz przejrzeć ten plik i zobaczyć wszystkie obręcze, przez które przechodzą Railsy, aby działał prawie bez konieczności martw się o to.put
ipost
zamiast nacommon
? Z przewodnika po zabezpieczeniach szyn :The solution to this is including a security token in non-GET requests
Angular_rails_csrf gem automatycznie dodaje wsparcie dla wzoru opisanego w odpowiedzi HungYuHei jest do wszystkich kontrolerów:
# Gemfile gem 'angular_rails_csrf'
źródło
angular_rails_csrf
gem nie działa z Railsami 5. Jednak skonfigurowanie nagłówków żądań Angular z wartością z metatagu CSRF działa!Odpowiedź, która łączy wszystkie poprzednie odpowiedzi i opiera się na używaniu
Devise
klejnotu uwierzytelniania.Przede wszystkim dodaj klejnot:
gem 'angular_rails_csrf'
Następnie dodaj
rescue_from
blok do 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
Na koniec dodaj moduł przechwytujący do aplikacji kątowej.
# 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')
źródło
$injector
zamiast tylko bezpośrednio$http
?Widziałem inne odpowiedzi i pomyślałem, że są świetne i dobrze przemyślane. Moja aplikacja rails działała jednak z czymś, co uważałem za prostsze rozwiązanie, więc pomyślałem, że się nim podzielę. Moja aplikacja rails zawiera to domyślne ustawienie,
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
Przeczytałem komentarze i wydawało mi się, że właśnie tego chcę użyć kątowego i uniknąć błędu csrf. Zmieniłem to na to,
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
A teraz działa! Nie widzę powodu, dla którego to nie powinno działać, ale chciałbym usłyszeć spostrzeżenia z innych plakatów.
źródło
W mojej aplikacji wykorzystałem treść odpowiedzi HungYuHei. Zauważyłem, że miałem do czynienia z kilkoma dodatkowymi problemami, jednak niektóre z powodu używania Devise do uwierzytelniania, a niektóre z powodu domyślnego ustawienia mojej aplikacji:
protect_from_forgery with: :exception
Zwracam uwagę na powiązane pytanie dotyczące przepełnienia stosu i odpowiedzi , a następnie napisałem znacznie bardziej szczegółowy wpis na blogu, który podsumowuje różne rozważania. Części tego rozwiązania, które są tutaj istotne, to w kontrolerze aplikacji:
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
źródło
Znalazłem bardzo szybki sposób na to. Wszystko, co musiałem zrobić, to:
za. Moim zdaniem inicjalizuję
$scope
zmienną, która zawiera token, powiedzmy przed formularzem, a jeszcze lepiej przy inicjalizacji kontrolera:<div ng-controller="MyCtrl" ng-init="authenticity_token = '<%= form_authenticity_token %>'">
b. W moim kontrolerze AngularJS przed zapisaniem nowego wpisu dodaję token do skrótu:
$scope.addEntry = -> $scope.newEntry.authenticity_token = $scope.authenticity_token entry = Entry.save($scope.newEntry) $scope.entries.push(entry) $scope.newEntry = {}
Nic więcej nie trzeba robić.
źródło
angular .module('corsInterceptor', ['ngCookies']) .factory( 'corsInterceptor', function ($cookies) { return { request: function(config) { config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN'); return config; } }; } );
Działa po stronie angularjs!
źródło