Rails CSRF Protection + Angular.js:protect_from_forgery让我注销POST


129

如果protect_from_forgery在application_controller中提到了该选项,那么我可以登录并执行任何GET请求,但是在第一个POST请求上,Rails重置了会话,这使我注销。

protect_from_forgery暂时关闭了该选项,但希望将其与Angular.js一起使用。有什么办法吗?


看看这有助于任何,其对设置HTTP头stackoverflow.com/questions/14183025/...
马克Rajcok

Answers:


276

我认为从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

18
我喜欢这项技术,因为您不必修改任何客户端代码。
Michelle Tilley

11
此解决方案如何保留CSRF保护的有用性?通过设置cookie,被标记的用户的浏览器将在所有后续请求(包括跨站点请求)上发送该cookie。我可以设置一个发送恶意请求的恶意第三方站点,并且用户的浏览器会将“ XSRF-TOKEN”发送到服务器。看来此解决方案等于完全关闭CSRF保护。
史蒂文

9
来自Angular文档:“由于只有在您的域上运行的JavaScript才能读取Cookie,因此可以确保您的服务器XHR来自在您的域上运行的JavaScript。” @StevenXu-第三方站点将如何读取Cookie?
吉米·贝克

8
@JimmyBaker:是的,你是对的。我已经查看了文档。该方法在概念上是正确的。我将cookie的设置与验证混淆了,但没有意识到Angular框架正在根据cookie的值设置自定义标头!
史蒂文

5
form_authenticity_token在Rails 4.2中的每个调用上都会生成新值,因此这似乎不再起作用。
戴夫

78

如果您使用默认的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的答案,它涵盖了服务器而不是客户端的所有基础。


让我解释。基本文档是纯HTML,不是.erb,因此我不能使用<%= csrf_meta_tags %>。我认为应该protect_from_forgery只提一下。该怎么办?基本文档必须是纯HTML(我不是此处选择的人)。
保罗,

3
当您使用的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令牌,并且它起作用了。怎么样?
保罗,

1
Rails的UJS驱动程序使用jQuery.ajaxPrefilter此处显示为:github.com/indirect/jquery-rails/blob/c1eb6ae/vendor/assets/...您可以仔细阅读这个文件并看到所有的箍的Rails通过跳跃,使工作几乎不必担心它。
米歇尔·提里

@BrandonTilley仅在putpost不是在这样做才有意义common吗?来自rails安全指南The solution to this is including a security token in non-GET requests
christianvuerings

29

angular_rails_csrf宝石会自动为所描述的模式支持HungYuHei的回答您的所有控制器:

# Gemfile
gem 'angular_rails_csrf'

知道如何配置应用程序控制器和其他与CSRF /伪造相关的设置,以正确使用angular_rails_csrf吗?
本·惠勒

在发表本文时,angular_rails_csrfgem无法与Rails 5一起使用。但是,使用CSRF元标记中的值配置Angular请求标头是可行的!
bideowego

有宝石的新版本,它支持Rails的5
jsanders

4

合并所有先前答案的答案,并取决于您使用的是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')

1
为什么要注射$injector而不是直接注射$http
whitehat101

这有效,但仅认为我添加的是检查请求是否已重复。当它重复时,我们将不再发送,因为它将永远循环。
duleorlovic's

1

我看到了其他答案,并认为它们很棒并且经过了深思熟虑。我的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

现在就可以了!我看不出有什么理由不起作用,但我很想听听其他海报的见识。


6
如果您尝试使用rails'sessions',这会引起问题,因为如果它没有通过伪造测试,它将被设置为nil,这将一直存在,因为您没有从客户端发送csrf令牌。
hajpoj

但是,如果您不使用Rails会话,一切都会很好;谢谢!我一直在努力寻找最干净的解决方案。
2014年

1

我在应用程序中使用了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

1

我发现了一个非常快的破解方法。我要做的只是以下内容:

一个。在我看来,我初始化了一个$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 = {}

不需要做更多的事情了。


0
 angular
  .module('corsInterceptor', ['ngCookies'])
  .factory(
    'corsInterceptor',
    function ($cookies) {
      return {
        request: function(config) {
          config.headers["X-XSRF-TOKEN"] = $cookies.get('XSRF-TOKEN');
          return config;
        }
      };
    }
  );

它正在angularjs方面工作!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.