我暂时关闭了protect_from_forgery选项,但想要与Angular.js一起使用它。有什么办法可以做到这一点吗?
我认为从DOM中读取CSRF值并不是一个好的解决方案,它只是一种权宜之计。
以下是来自AngularJS官网的文档http://docs.angularjs.org/api/ng.$http:
由于只有在您的域上运行的JavaScript才能读取cookie,因此您的服务器可以确定XHR是否来自JavaScript在您的域上运行。
要利用此(CSRF保护),您的服务器需要在第一个HTTP GET请求中设置一个令牌,该令牌存储在JavaScript可读的会话cookie XSRF-TOKEN中。在随后的非GET请求中,服务器可以验证cookie是否与X-XSRF-TOKEN HTTP标头相匹配
这是基于这些说明的我的解决方案:
首先,设置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 %>
),你可以像这样配置你的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文件(我在这里不是选择者)。 - Paulprotect_from_forgery
时,实际上是在告诉Rails:"当我的JavaScript代码发起Ajax请求时,我保证在请求头中发送一个对应当前CSRF令牌的X-CSRF-Token
"。为了获取这个令牌,Rails会用<%= csrf_meta_token %>
将它注入到DOM中,并在每次发起Ajax请求时(默认情况下,Rails 3 UJS驱动程序会代劳),使用jQuery获取meta标签的内容。如果你没有使用ERB,则无法从Rails中获取当前令牌并将其传递给页面和/或JavaScript代码--因此,你不能使用protect_from_forgery
以这种方式进行防护。 - Michelle TilleyjQuery.ajaxPrefilter
,如此所示:https://github.com/indirect/jquery-rails/blob/c1eb6ae/vendor/assets/javascripts/jquery_ujs.js#L294 您可以查看此文件并查看Rails跳过的所有障碍,使其基本上无需担心即可正常工作。 - Michelle Tilley$ ('meta [name = csrf-token]') .attr('content')
的情况下访问csrfToken
?由于携带XSRF-TOKEN
的$cookies
在config
块中不可用,并且在run
中更新headers
没有任何好处。 - red-devil$httpProvider.defaults.headers.delete['X-CSRF-Token'] = csrfToken
引发了以下错误:$injector:modulerr] 因为 undefined 不是一个对象 (评估 '$httpProvider.defaults.headers["delete"]['X-CSRF-Token'] = csrfToken'),无法实例化 AngulaRails 模块。
- ctilley79angular_rails_csrf gem会自动为您的所有控制器添加HungYuHei's answer中描述的模式支持:
# Gemfile
gem 'angular_rails_csrf'
angular_rails_csrf
gem 与 Rails 5 不兼容。然而,使用 CSRF meta 标签的值配置 Angular 请求头可以解决问题! - bideowego这个答案汇总了之前所有的答案,并假设您正在使用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')
$injector
而不是直接注入 $http
? - whitehat101<div ng-controller="MyCtrl" ng-init="authenticity_token = '<%= form_authenticity_token %>'">
$scope.addEntry = ->
$scope.newEntry.authenticity_token = $scope.authenticity_token
entry = Entry.save($scope.newEntry)
$scope.entries.push(entry)
$scope.newEntry = {}
什么都不需要做。
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
angular
.module('corsInterceptor', ['ngCookies'])
.factory(
'corsInterceptor',
function ($cookies) {
return {
request: function(config) {
config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
return config;
}
};
}
);
它正在AngularJS方面工作!