Rails CSRF Protection + Angular.js: protect_from_forgery заставляет меня выйти из системы на POST
Если protect_from_forgery
опция упоминается в application_controller, тогда я могу войти в систему и выполнить любые запросы GET, но при самом первом запросе POST Rails сбрасывает сессию, которая выводит меня из системы.
Я перевернул protect_from_forgery
опция временно отключена, но хотелось бы использовать ее с Angular.js. Есть ли способ сделать это?
8 ответов
Я думаю, что чтение CSRF-значения из DOM не является хорошим решением, это просто обходной путь.
Вот форма документа angularJS, официальный сайт https://docs.angularjs.org/api/ng/service/$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_action :set_csrf_cookie
def set_csrf_cookie
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 %>
), вы можете настроить свой угловой модуль следующим образом:
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, который охватывает все базы на сервере, а не на клиенте.
Драгоценный камень angular_rails_csrf автоматически добавляет поддержку шаблона, описанного в ответе HungYuHei, на все ваши контроллеры:
# Gemfile
gem 'angular_rails_csrf'
Ответ, который объединяет все предыдущие ответы и полагается, что вы используете Devise
аутентификационный камень.
Прежде всего, добавьте драгоценный камень:
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
И наконец, добавьте модуль перехватчика в ваше угловое приложение.
# 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')
Я видел другие ответы и думал, что они великолепны и хорошо продуманы. Я получил свое приложение rails, хотя с тем, что я думал, было более простым решением, поэтому я решил поделиться. Мое приложение 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
И теперь это работает! Я не вижу причин, почему это не должно работать, но я бы хотел услышать некоторые идеи от других авторов.
Я использовал содержание ответа 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
Я нашел очень быстрый взломать это. Все, что мне нужно было сделать, это следующее:
а. На мой взгляд, я инициализирую $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 = {}
Больше ничего не нужно делать.
angular
.module('corsInterceptor', ['ngCookies'])
.factory(
'corsInterceptor',
function ($cookies) {
return {
request: function(config) {
config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
return config;
}
};
}
);
Это работает на стороне angularjs!