Rails [已解决] 使用 Ajax 时遇到了 InvalidAuthenticityToken 错误

blueplanet · 发布于 2014年2月07日 · 最后由 kgtonglousy 回复于 2014年2月13日 · 2474 次阅读
2650
  • 在 Rails 页面上使用 $.Ajax 发送一个最简单的的post请求,结果遇到了InvalidAuthenticityToken错误
  • 如果使用form_for ... remote:true的话完全正常
  • 我明白Railshelper里面自动加了X-CSRF-Token:

我的问题是:难道在不使用remote: true的时候,都需要自己在Ajax请求里面加上X-CSRF-Token:么?

版本信息

  • OS : MacOS 10.9.2
  • Rails : 4.0.2
  • Ruby : 2.0.0-p353

代码

  • 前提:rails g scaffold posts title content:text
  • views/posts/index.html.erb
<form action="/posts" method="POST">
  <input type="text" id="title">
  <input type="submit" id="ajax_submit">
</form>
  • assets/javascript/posts.js.coffee
$ ->
  $('#ajax_submit').click (event) ->
    event.preventDefault() # 最开始的时候忘记加这句了,导致默认的提交动作也被执行了
    $.ajax(
      type: "POST"
      url: "/posts"
      data: {
        title: $('#post_title')
      }
      success: ->
        alert "success"
    )

分析过程(jquery_ujs.js)

  • 首先,有一个rails.CSRFProtection函数。它的作用就是向ajax请求的headers中加入csrf token信息,代码如下
CSRFProtection: function(xhr) {
  var token = $('meta[name="csrf-token"]').attr('content');
  if (token) xhr.setRequestHeader('X-CSRF-Token', token);
},
  • 其次,第290行有如下的代码
if (rails.fire($document, 'rails:attachBindings')) { # 这句是触发`rails:attachBindings`事件
  $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
  • rails:attachBindings事件:查看change log可以看到,是为了增加$.rails的自定义设置。所以没有自定义的前提下,触发事件这句代码不会失败
## 2.0.3 (16 August 2012)

  - Updated to latest jquery-ujs
    - created `rails:attachBindings` to allow for customization of $.rails object settings
    - created `ajax:send` event to provide access to jqXHR object from ajax requests
    - added support for `data-with-credentials`
  • 也就意味着,$.ajaxPrefilter(...这一句肯定会被执行
  • 也就是说:引入了jquery_ujs.js的前提下,所有的ajax请求都会自动被加入csrf token

结论

  • rails项目中,所有非跨站的ajax请求都会被自动加入csrf token信息,不需要自己去做
共收到 8 条回复
250
miclle · #1 · 2014年2月07日

是这样的

jquery_ujs.js

...
// Make sure that every Ajax request sends the CSRF token
    CSRFProtection: function(xhr) {
      var token = $('meta[name="csrf-token"]').attr('content');
      if (token) xhr.setRequestHeader('X-CSRF-Token', token);
    },
...
2650
blueplanet · #2 · 2014年2月07日

#1楼 @miclle 在jquery_ujs.js的第292行有这样的代码

$.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});

所以我认为应该是在所有的Ajax之前都会自动执行CSRFProtection这个函数啊

250
miclle · #3 · 2014年2月07日

#2楼 @blueplanet 这些代码放在了这样一个判断里面:

if (rails.fire($document, 'rails:attachBindings')) {

...
250
miclle · #4 · 2014年2月07日

你自己可以写一个全局的 $.ajaxPrefilter 去设置 'X-CSRF-Token'

2650
blueplanet · #5 · 2014年2月07日

if (rails.fire($document, 'rails:attachBindings')) {

不好意思,这句代码我没看懂。请问,这种冒号分割的是什么意思?

2650
blueplanet · #6 · 2014年2月11日

#4楼 @miclle 非常感谢你的信息。是我自己的代码里犯了一个非常初级的错误。 在ajax的函数里忘记加下面这一行了。加上之后就没有问题,不会出现InvalidAuthenticityToken错误了。 帖子的内容我已经修改了,按照现在的代码,就不会出现错误了。

event.preventDefault() # 最开始的时候忘记加这句了,导致默认的提交动作也被执行了
273
ruby_sky · #7 · 2014年2月11日

你还应该继续搞清楚 X-CSRF-Token 的生成过程, protect_from_forgery 与 csrf_meta_tags 之间的关系。Rails是如何避免CSRF的。

2650
blueplanet · #8 · 2014年2月12日

#7楼 @ruby_sky 谢谢你的意见,这就去研究一下!

9楼 已删除
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册