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

Pau*_*aul 126 ruby-on-rails csrf protect-from-forgery angularjs

如果protect_from_forgery在application_controller中提到了该选项,那么我可以登录并执行任何GET请求,但是在第一次POST请求时,Rails会重置会话,这会导致我退出.

protect_from_forgery暂时关闭了该选项,但想将它与Angular.js一起使用.有办法做到这一点吗?

小智 274

我认为从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
Run Code Online (Sandbox Code Playgroud)

然后,我们应该在每个非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
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这种技术,因为您不必修改任何客户端代码. (18认同)
  • 该解决方案如何保留CSRF保护的有用性?通过设置cookie,标记用户的浏览器将在所有后续请求(包括跨站点请求)上发送该cookie.我可以设置一个发送恶意请求的恶意第三方站点,用户的浏览器会向服务器发送"XSRF-TOKEN".看起来这个解决方案无异于完全关闭CSRF保护. (11认同)
  • 来自Angular文档:"由于只有在您的域上运行的JavaScript才能读取cookie,因此您的服务器可以确保XHR来自您域上运行的JavaScript." @StevenXu - 第三方网站如何读取cookie? (9认同)
  • @JimmyBaker:是的,你是对的.我已经查看了文档.这种方法在概念上是合理的.我把cookie的设置与验证混淆了,没有意识到Angular框架是根据cookie的值设置自定义头! (8认同)
  • form_authenticity_token在Rails 4.2中的每个调用上生成新值,因此这似乎不再起作用. (5认同)
  • 谢谢.我喜欢这个.值得一提的是,protect_from_forgery必须仍然存在才能使此代码正常工作. (2认同)
  • 对我来说(Rails 4.2.6),验证的请求方法必须从cookies变量中读取:`super || valid_authenticity_token?(session,cookies ['XSRF-TOKEN'])` (2认同)

Mic*_*ley 78

如果您使用的是默认的Rails CSRF保护(<%= csrf_meta_tags %>),则可以像下面这样配置Angular模块:

myAngularApp.config ["$httpProvider", ($httpProvider) ->
  $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
]
Run Code Online (Sandbox Code Playgroud)

或者,如果你没有使用CoffeeScript(什么!?):

myAngularApp.config([
  "$httpProvider", function($httpProvider) {
    $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
  }
]);
Run Code Online (Sandbox Code Playgroud)

如果您愿意,可以仅在非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
]
Run Code Online (Sandbox Code Playgroud)

另外,请务必查看HungYuHei的答案,其中涵盖了服务器上的所有基础,而不是客户端.

  • 当你使用`protect_from_forgery`时,你说的是"当我的JavaScript代码发出Ajax请求时,我保证在与当前CSRF令牌对应的标题中发送一个`X-CSRF-Token`." 为了得到这个令牌,Rails用`<%= csrf_meta_token%>`将它注入到DOM中,并且只要它发出Ajax请求,get就会获得带有jQuery的元标记的内容(默认的Rails 3 UJS驱动程序为你做这个) .如果您没有使用ERB,则无法将当前令牌从Rails获取到页面和/或JavaScript中 - 因此您无法以这种方式使用`protect_from_forgery`. (3认同)

小智 29

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

# Gemfile
gem 'angular_rails_csrf'
Run Code Online (Sandbox Code Playgroud)