Если параметр protect_from_forgery
указан в application_controller, то я могу входить в систему и выполнять любые запросы GET, но при первом запросе POST Rails сбрасывает сеанс, который выводит меня из системы.
Я временно отключил параметр protect_from_forgery
, но хотел бы использовать его с Angular.js. Есть ли способ сделать это?
Я думаю, что чтение 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
Если вы используете стандартную защиту 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, который охватывает все базы на сервере, а не клиент.
<%= csrf_meta_tags %>
. Я думал, что должно быть достаточно, чтобы упомянуть только protect_from_forgery
. Что делать? Базовый документ должен быть простым HTML (я здесь не тот, кто выбирает).
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
таким образом.
angular_rails_csrf gem автоматически добавляет поддержку шаблона, описанного в ответ HungYuHei всем вашим контроллерам:
# Gemfile
gem 'angular_rails_csrf'
Ответ, который объединяет все предыдущие ответы, и полагается, что вы используете 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')
$injector
вместо того, чтобы просто вводить $http
?
Я очень быстро взломал это. Все, что мне нужно было сделать, это следующее:
а. На мой взгляд, я инициализирую переменную $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 = {}
Больше ничего не нужно делать.
Я использовал контент из ответа 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
Я увидел другие ответы и подумал, что они великолепны и хорошо продуманы. Я получил приложение для рельсов, хотя с тем, что, по моему мнению, было более простым решением, поэтому я решил поделиться с вами. В моем приложении 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
И теперь это работает! Я не вижу причин, почему это не должно работать, но я хотел бы услышать некоторое понимание других плакатов.
angular
.module('corsInterceptor', ['ngCookies'])
.factory(
'corsInterceptor',
function ($cookies) {
return {
request: function(config) {
config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
return config;
}
};
}
);
Работает с угловой стороной!