Rails CSRF Protection + Angular.js: protected_from_forgery 让我退出 POST

如果 application _ controller 中提到了 protect_from_forgery选项,那么我就可以登录并执行任何 GET 请求,但是在第一次 POST 请求时,Rails 会重置会话,这会使我退出。

我暂时关闭了 protect_from_forgery选项,但是我想在 Angular.js 中使用它。有什么办法吗?

47378 次浏览

如果您正在使用默认的 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
]

另外,一定要检查 红玉喜的回答,它涵盖了服务器上而不是客户机上的所有基础。

我认为从 DOM 读取 CSRF 值不是一个好的解决方案,它只是一个变通方案。

这里是一个文件形式 angularJS 官方网站 http://docs.angularjs.org/api/ng.$http:

因为只有在您的域上运行的 JavaScript 才能读取 cookie,所以您的服务器可以确信 XHR 来自于在您的域上运行的 JavaScript。

要利用这一点(CSRF 保护) ,服务器需要在 JavaScript 可读会话中设置一个令牌 在第一个 HTTPGET 请求上称为 XSRF-TOKEN 的 cookie 服务器可以验证 Cookie 是否匹配的非 GET 请求 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 应用程序工作,虽然我认为这是一个更简单的解决方案,所以我想我会分享。我的 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

我读了评论,似乎这就是我想要使用的角度和避免 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 _ rails _ csrf gem 自动为所有控制器添加对 红玉喜的回答中描述的模式的支持:

# Gemfile
gem 'angular_rails_csrf'

我已经在申请中使用了红玉喜答案中的内容。然而,我发现我正在处理一些额外的问题,一些是因为我使用了 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

合并了以前所有答案的答案依赖于您使用的是 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')

我找到了一个非常快速的黑客程序,我要做的就是:

一。在我的视图中,我初始化了一个包含令牌的 $scope变量,比方说在表单之前,或者在控制器初始化时更好:

<div ng-controller="MyCtrl" ng-init="authenticity_token = '<%= form_authenticity_token %>'">

在 AngularJS 控制器中,在保存新条目之前,我将令牌添加到 hash:

$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 那边起作用了!