Answers:
我认为从DOM读取CSRF值不是一个好的解决方案,这只是一种解决方法。
这是angularJS官方网站http://docs.angularjs.org/api/ng.$http的文档格式 :
由于只有在您的域上运行的JavaScript才能读取Cookie,因此可以确保您的服务器XHR来自在您的域上运行的JavaScript。
要利用此优势(CSRF保护),您的服务器需要在第一个HTTP GET请求上的名为XSRF-TOKEN的JavaScript可读会话cookie中设置令牌。在后续的非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(我不是此处选择的人)。
protect_from_forgery
意思是“当我的JavaScript代码发出Ajax请求时,我保证会X-CSRF-Token
在与当前CSRF令牌相对应的标头中发送一个”。为了获得此令牌,Rails <%= csrf_meta_token %>
每次向Ajax请求时,都会使用jQuery 将其注入DOM 并使用jQuery获取meta标签的内容(默认的Rails 3 UJS驱动程序会为您执行此操作)。如果您不使用ERB,则无法将当前令牌从Rails放入页面和/或JavaScript中-因此,您不能protect_from_forgery
以这种方式使用。
csrf_meta_tags
每次服务器生成响应时,以及每次这些标记与以前的标记不同时,客户端都会收到一次。因此,这些标记对于每个请求都是唯一的。问题是:应用程序如何接收AJAX请求的这些标签(无角度)?我在jQuery POST请求中使用了protect_from_forgery,从没有打扰自己获取此CSRF令牌,并且它起作用了。怎么样?
jQuery.ajaxPrefilter
此处显示为:github.com/indirect/jquery-rails/blob/c1eb6ae/vendor/assets/...您可以仔细阅读这个文件并看到所有的箍的Rails通过跳跃,使工作几乎不必担心它。
put
而post
不是在这样做才有意义common
吗?来自rails安全指南:The solution to this is including a security token in non-GET requests
该angular_rails_csrf宝石会自动为所描述的模式支持HungYuHei的回答您的所有控制器:
# Gemfile
gem 'angular_rails_csrf'
angular_rails_csrf
gem无法与Rails 5一起使用。但是,使用CSRF元标记中的值配置Angular请求标头是可行的!
合并所有先前答案的答案,并取决于您使用的是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
最后,将拦截器模块添加到您的角度应用程序中。
# 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
?
我看到了其他答案,并认为它们很棒并且经过了深思熟虑。我的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 %>'">
b。在我的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方面工作!