Django CSRF检查失败,并带有Ajax POST请求


180

我可以通过我的AJAX帖子向遵循Django CSRF保护机制的人员提供帮助。我按照这里的指示进行:

http://docs.djangoproject.com/en/dev/ref/contrib/csrf/

我已经完全复制了该页面上的AJAX示例代码:

http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax

getCookie('csrftoken')xhr.setRequestHeader呼叫之前打印了警报内容,并确实在其中填充了一些数据。我不确定如何验证令牌是否正确,但是我鼓励它正在查找并发送一些消息。

但是Django仍然拒绝我的AJAX帖子。

这是我的JavaScript:

$.post("/memorize/", data, function (result) {
    if (result != "failure") {
        get_random_card();
    }
    else {
        alert("Failed to save card data.");
    }
});

这是我在Django中看到的错误:

[23 / Feb / 2011 22:08:29]“ POST / memorize / HTTP / 1.1” 403 2332

我确定我缺少某些东西,也许很简单,但是我不知道它是什么。我在SO周围搜索,并看到了一些有关通过csrf_exempt装饰器关闭CSRF检查以获取我的视图的信息,但是我发现这没有吸引力。我已经尝试过了,并且可以工作,但是我宁愿让POST按照Django期望的方式工作。

以防万一,这是我的观点正在做的要点:

def myview(request):

    profile = request.user.profile

    if request.method == 'POST':
        """
        Process the post...
        """
        return HttpResponseRedirect('/memorize/')
    else: # request.method == 'GET'

        ajax = request.GET.has_key('ajax')

        """
        Some irrelevent code...
        """

        if ajax:
            response = HttpResponse()
            profile.get_stack_json(response)
            return response
        else:
            """
            Get data to send along with the content of the page.
            """

        return render_to_response('memorize/memorize.html',
                """ My data """
                context_instance=RequestContext(request))

感谢您的回复!


1
您使用的是哪个版本的django?
zsquare 2011年

您是否添加了正确的CSRF中间件类并以正确的顺序放置它们?
达伦(Darren)

Jakub在下面回答了我的问题,但以防万一它对其他人有用:@zsquare:版本1.2.3。@mongoose_za:是的,它们以正确的顺序添加。
firebush

Answers:


181

真正的解决方案

好的,我设法找出问题所在。它位于Javascript(正如我在下面建议的)代码中。

您需要的是:

$.ajaxSetup({ 
     beforeSend: function(xhr, settings) {
         function getCookie(name) {
             var cookieValue = null;
             if (document.cookie && document.cookie != '') {
                 var cookies = document.cookie.split(';');
                 for (var i = 0; i < cookies.length; i++) {
                     var cookie = jQuery.trim(cookies[i]);
                     // Does this cookie string begin with the name we want?
                     if (cookie.substring(0, name.length + 1) == (name + '=')) {
                         cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                         break;
                     }
                 }
             }
             return cookieValue;
         }
         if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
             // Only send the token to relative URLs i.e. locally.
             xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
         }
     } 
});

而不是官方文档中发布的代码:https//docs.djangoproject.com/en/2.2/ref/csrf/

工作代码来自以下Django条目:http : //www.djangoproject.com/weblog/2011/feb/08/security/

因此,一般的解决方案是:“使用ajaxSetup处理程序而不是ajaxSend处理程序”。我不知道为什么会这样。但这对我有用:)

以前的帖子(无答案)

我实际上遇到了同样的问题。

它在更新到Django 1.2.5之后发生-在Django 1.2.4中AJAX POST请求没有错误(AJAX不受任何保护,但效果很好)。

就像OP一样,我尝试了Django文档中发布的JavaScript代码段。我正在使用jQuery 1.5。我也在使用“ django.middleware.csrf.CsrfViewMiddleware”中间件。

我尝试遵循中间件代码,但我知道它在此方面失败:

request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '')

然后

if request_csrf_token != csrf_token:
    return self._reject(request, REASON_BAD_TOKEN)

此“如果”为true,因为“ request_csrf_token”为空。

基本上,这意味着未设置标头。那么此JS行有什么问题吗:

xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));

我希望提供的详细信息将有助于我们解决问题:)


这工作!当您将其粘贴到上面时,我放入了.ajaxSetup函数,现在可以发布,而不会出现403错误。Jakub,感谢您分享解决方案。好发现。:)
firebush

使用ajaxSetup而不是ajaxSend与jQuery文档相反:api.jquery.com/jQuery.ajaxSetup
Mark Lavin

使用1.3,官方的django文档条目对我有用。
2011年

1
我想,但这似乎并没有为我工作,我使用jQuery v1.7.2的,我的问题是stackoverflow.com/questions/11812694/...
做白日梦

我必须在我的视图函数中添加注释@ensure_csrf_cookie,以在从移动设备请求页面时强制设置csrf cookie。
凯恩2014年

169

如果使用该$.ajax函数,则可以简单地将csrf令牌添加到数据主体中:

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },

2
当我使用带标记的答案时,它对我有用,但是,如果我在这里使用您的解决方案,则不行。但是您的解决方案应该可以工作,但我不知道为什么不行。Django 1.4还有什么需要做的吗?
霍曼2012年

1
谢谢!很简单。仍然适用于Django 1.8和jQuery 2.1.3
Alejandro Veintimilla

19
此解决方案需要将javascript嵌入模板中吗?
Mox

15
@Mox:将其放在html中,但在您的Js文件上方是ajax函数<script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>
2015年

谢谢!如此简单而优雅。它适用于Django 1.8。我在通话中将其添加csrfmiddlewaretoken: '{{ csrf_token }}'data字典中$.post
帕维尔·尤达耶夫

75

将此行添加到您的jQuery代码中:

$.ajaxSetup({
  data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});

并做了。


我试过了,除了我的表单有文件上传。我的后端是django,仍然收到错误400CSRF Failed: CSRF token missing or incorrect.
Hussain

16

问题是因为django希望将cookie中的值作为表单数据的一部分传递回去。上一个答案中的代码使javascript能够找出cookie值并将其放入表单数据中。从技术的角度来看,这是一种不错的方法,但是它确实有些冗长。

过去,我更简单地通过获取JavaScript将令牌值放入发布数据中来完成此操作。

如果您在模板中使用{%csrf_token%},则会发出带有该值的隐藏表单字段。但是,如果您使用{{csrf_token}},则只会获得令牌的裸值,因此可以在javascript中使用此代码...。

csrf_token = "{{ csrf_token }}";

然后,您可以在哈希中添加所需的键名称,然后将其作为数据提交给ajax调用。


@aehlke您可以拥有静态文件。在源代码中,您可以看到一个不错的示例,在该示例中将django变量注册window对象中,以便以后可以访问它们。即使在静态文件中。
KitKat 2014年

3
@KitKat的确是:)很抱歉在这里给我古老的,无知的评论。好点子。
aehlke 2014年

重新静态文件。这不是问题,如果您不介意html的话。我只是将{{csrf_token}}放在了主要的html模板中,离requirejs的要求不远。像魅力一样运作。
JL Peyret 2015年

14

{% csrf_token %}在HTML模板放里面<form></form>

转换为:

<input type='hidden' name='csrfmiddlewaretoken' value='Sdgrw2HfynbFgPcZ5sjaoAI5zsMZ4wZR' />

所以为什么不像这样在您的JS中grep它:

token = $("#change_password-form").find('input[name=csrfmiddlewaretoken]').val()

然后通过它,例如进行一些POST,例如:

$.post( "/panel/change_password/", {foo: bar, csrfmiddlewaretoken: token}, function(data){
    console.log(data);
});

9

非jQuery答案:

var csrfcookie = function() {
    var cookieValue = null,
        name = 'csrftoken';
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
};

用法:

var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.setRequestHeader('X-CSRFToken', csrfcookie());
request.onload = callback;
request.send(data);

8

如果您的表单在没有JS的Django中正确发布,那么您应该能够使用Ajax逐步增强它,而不会造成任何黑客攻击或csrf令牌的混乱传递。只需序列化整个表单,它将自动选择所有表单字段,包括隐藏的csrf字段:

$('#myForm').submit(function(){
    var action = $(this).attr('action');
    var that = $(this);
    $.ajax({
        url: action,
        type: 'POST',
        data: that.serialize()
        ,success: function(data){
            console.log('Success!');
        }
    });
    return false;
});

我已经使用Django 1.3+和jQuery 1.5+进行了测试。显然,这将适用于任何HTML表单,而不仅仅是Django应用。


5

将Firefox与Firebug结合使用。触发ajax请求时,打开“控制台”选项卡。这样一来,DEBUG=True您将得到漂亮的django错误页面作为响应,甚至可以在控制台选项卡中看到ajax响应的呈现的html。

然后,您将知道错误是什么。


5

可接受的答案很可能是鲱鱼。Django 1.2.4和1.2.5之间的区别是AJAX请求需要CSRF令牌。

我在Django 1.3上遇到了这个问题,这是由于未设置CSRF cookie引起的首先。除非必须,否则Django不会设置cookie。因此,运行在Django 1.2.4上的排他或大量ajax站点可能永远不会向客户端发送令牌,然后需要令牌的升级会导致403错误。

理想的解决方法是在这里:http : //docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
但您必须等待1.4,除非这只是跟上代码的文档

编辑

还要注意,以后的Django文档注意到jQuery 1.5中的一个错误,因此请确保您将Django建议的代码与1.5.1或更高版本一起使用:http : //docs.djangoproject.com/en/1.3/ref/contrib/csrf/#阿贾克斯


我的回答在撰写本文时是准确的:)就在Django从1.2.4更新到1.2.5之后。也是当最新的jQuery版本是1.5时。事实证明,问题的根源是错误的jQuery(1.5),并且正如您所说的那样,此信息现在已添加到Django文档中。在我的情况下:设置了Cookie,并且未将令牌添加到AJAX请求中。给出的修复程序可用于错误的jQuery 1.5。到目前为止,您可以使用此处提供的示例代码并使用最新的jQuery来坚持使用官方文档。您的问题的来源与此处讨论的问题不同:)
JakubGocławski2011年

2
现在有一个装饰器ensure_csrf_cookie,您可以包装一个视图以确保它发送cookie。
Brian Neal

这是我遇到的问题,csrftoken首先没有cookie,谢谢!
crhodes

5

似乎没有人提到过使用X-CSRFTokenheader和来在纯JS中执行此操作的方法{{ csrf_token }},因此这是一个简单的解决方案,您无需搜索cookie或DOM:

var xhttp = new XMLHttpRequest();
xhttp.open("POST", url, true);
xhttp.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
xhttp.send();

4

由于当前答案中没有任何地方说明,因此如果您不将 js 嵌入模板中,最快的解决方案是:

<script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>你的模板里面引用之前的script.js文件,然后添加csrfmiddlewaretoken到您的data字典在你的js文件:

$.ajax({
            type: 'POST',
            url: somepathname + "do_it/",
            data: {csrfmiddlewaretoken: window.CSRF_TOKEN},
            success: function() {
                console.log("Success!");
            }
        })

2

我刚刚遇到了一些不同但相似的情况。不能100%确定是否可以解决您的问题,但是我通过设置POST参数'csrfmiddlewaretoken'并使用适当的cookie值字符串解决了Django 1.3的问题,该参数通常由Django的home HTML形式返回。带有'{%csrf_token%}'标签的模板系统。我没有尝试使用较旧的Django,只是在Django1.3上发生并解决了。我的问题是,通过Ajax从表单提交的第一个请求已成功完成,但从完全相同的第二次请求失败,导致了403状态,即使标头'X-CSRFToken'也已正确放置CSRF令牌值就像第一次尝试一样。希望这可以帮助。

问候,


2

您可以将此js粘贴到您的html文件中,请记住将其放在其他js函数之前

<script>
  // using jQuery
  function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
      var cookies = document.cookie.split(';');
      for (var i = 0; i < cookies.length; i++) {
        var cookie = jQuery.trim(cookies[i]);
        // Does this cookie string begin with the name we want?
        if (cookie.substring(0, name.length + 1) == (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  }

  function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }

  $(document).ready(function() {
    var csrftoken = getCookie('csrftoken');
    $.ajaxSetup({
      beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
          xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
      }
    });
  });
</script>


1

每个会话(即每次登录)都分配一个CSRF令牌。因此,在希望获取用户输入的某些数据并将其作为ajax调用发送给受csrf_protect装饰器保护的某个函数之前,请先尝试查找正在被调用的函数,然后再从用户获取此数据。例如,必须渲染一些模板,以便用户在其上输入数据。该模板由某些功能渲染。在此函数中,您可以按以下方式获取csrf令牌:csrf = request.COOKIES ['csrftoken']现在在上下文字典中传递此csrf值,针对该字典呈现相关模板。现在在该模板中编写以下行:现在,在您的javascript函数中,在发出ajax请求之前,请编写:var csrf = $('#csrf')。val()这将选择传递给模板的令牌的值并将其存储在变量csrf中。现在,在进行ajax调用时,在您的帖子数据中,还要传递此值:“ csrfmiddlewaretoken”:csrf

即使您未实现Django表单,这也将起作用。

实际上,这里的逻辑是:您需要可以从请求中获取的令牌。因此,您只需要确定登录后立即调用的函数即可。一旦有了此令牌,就可以再次调用ajax来获取它,或者将其传递给可被ajax访问的模板。


结构不是很好,但是解释得很好。我的问题是,我是以这种方式发送csrf的:csrftoken: csrftoken而不是csrfmiddlwaretoken: csrftoken。更改后,它起作用了。谢谢
几乎是初学者

1

对于遇到此问题并尝试调试的人:

1)django csrf检查(假设您要发送一个)是 这里

2)在我的情况下,将settings.CSRF_HEADER_NAME其设置为“ HTTP_X_CSRFTOKEN”,而我的AJAX调用正在发送名为“ HTTP_X_CSRF_TOKEN”的标头,因此无法正常工作。我可以在AJAX调用或Django设置中更改它。

3)如果您选择在服务器端进行更改,请找到django的安装位置,并csrf middleware在使用的.f中放置一个断点virtualenv,它将类似于:~/.envs/my-project/lib/python2.7/site-packages/django/middleware/csrf.py

import ipdb; ipdb.set_trace() # breakpoint!!
if request_csrf_token == "":
    # Fall back to X-CSRFToken, to make things easier for AJAX,
    # and possible for PUT/DELETE.
    request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')

然后,确保csrf令牌是从request.META正确获取的。

4)如果您需要更改标题等,请在设置文件中更改该变量



0

在我的情况下,问题出在我将nginx配置从主服务器复制到一个临时的服务器,并禁用了第二个服务器上不需要的https。

我必须在配置中注释掉这两行以使其再次起作用:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;

0

这是Django提供的较为简单的解决方案:

<script type="text/javascript">
// using jQuery
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
// set csrf header
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

// Ajax call here
$.ajax({
    url:"{% url 'members:saveAccount' %}",
    data: fd,
    processData: false,
    contentType: false,
    type: 'POST',
    success: function(data) {
        alert(data);
        }
    });
</script>

来源:https : //docs.djangoproject.com/en/1.11/ref/csrf/

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.