Django CSRF check failing with an Ajax POST reques

2018-12-31 07:33发布

I could use some help complying with Django's CSRF protection mechanism via my AJAX post. I've followed the directions here:

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

I've copied the AJAX sample code they have on that page exactly:

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

I put an alert printing the contents of getCookie('csrftoken') before the xhr.setRequestHeader call and it is indeed populated with some data. I'm not sure how to verify that the token is correct, but I'm encouraged that it's finding and sending something.

But Django is still rejecting my AJAX post.

Here's my JavaScript:

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

Here's the error I'm seeing from Django:

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

I'm sure I'm missing something, and maybe it's simple, but I don't know what it is. I've searched around SO and saw some information about turning off the CSRF check for my view via the csrf_exempt decorator, but I find that unappealing. I've tried that out and it works, but I'd rather get my POST to work the way Django was designed to expect it, if possible.

Just in case it's helpful, here's the gist of what my view is doing:

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))

Thanks for your replies!

18条回答
无色无味的生活
2楼-- · 2018-12-31 07:46

The {% csrf_token %} put in html templates inside <form></form>

translates to something like:

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

so why not just grep it in your JS like this:

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

and then pass it e.g doing some POST, like:

$.post( "/panel/change_password/", {foo: bar, csrfmiddlewaretoken: token}, function(data){
    console.log(data);
});
查看更多
只靠听说
3楼-- · 2018-12-31 07:48

If someone is strugling with axios to make this work this helped me:

import axios from 'axios';

axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'

Source: https://cbuelter.wordpress.com/2017/04/10/django-csrf-with-axios/

查看更多
后来的你喜欢了谁
4楼-- · 2018-12-31 07:50

The accepted answer is most likely a red herring. The difference between Django 1.2.4 and 1.2.5 was the requirement for a CSRF token for AJAX requests.

I came across this problem on Django 1.3 and it was caused by the CSRF cookie not being set in the first place. Django will not set the cookie unless it has to. So an exclusively or heavily ajax site running on Django 1.2.4 would potentially never have sent a token to the client and then the upgrade requiring the token would cause the 403 errors.

The ideal fix is here: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
but you'd have to wait for 1.4 unless this is just documentation catching up with the code

Edit

Note also that the later Django docs note a bug in jQuery 1.5 so ensure you are using 1.5.1 or later with the Django suggested code: http://docs.djangoproject.com/en/1.3/ref/contrib/csrf/#ajax

查看更多
千与千寻千般痛.
5楼-- · 2018-12-31 07:52

The issue is because django is expecting the value from the cookie to be passed back as part of the form data. The code from the previous answer is getting javascript to hunt out the cookie value and put it into the form data. Thats a lovely way of doing it from a technical point of view, but it does look a bit verbose.

In the past, I have done it more simply by getting the javascript to put the token value into the post data.

If you use {% csrf_token %} in your template, you will get a hidden form field emitted that carries the value. But, if you use {{ csrf_token }} you will just get the bare value of the token, so you can use this in javascript like this....

csrf_token = "{{ csrf_token }}";

Then you can include that, with the required key name in the hash you then submit as the data to the ajax call.

查看更多
姐姐魅力值爆表
6楼-- · 2018-12-31 07:52

It seems nobody has mentioned how to do this in pure JS using the X-CSRFToken header and {{ csrf_token }}, so here's a simple solution where you don't need to search through the cookies or the DOM:

var xhttp = new XMLHttpRequest();
xhttp.open("POST", url, true);
xhttp.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
xhttp.send();
查看更多
刘海飞了
7楼-- · 2018-12-31 07:54

Real solution

Ok, I managed to trace the problem down. It lies in the Javascript (as I suggested below) code.

What you need is this:

$.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'));
         }
     } 
});

instead of the code posted in the official docs: http://docs.djangoproject.com/en/1.2/ref/contrib/csrf/#ajax

The working code, comes from this Django entry: http://www.djangoproject.com/weblog/2011/feb/08/security/

So the general solution is: "use ajaxSetup handler instead of ajaxSend handler". I don't know why it works. But it works for me :)

Previous post (without answer)

I'm experiencing the same problem actually.

It occurs after updating to Django 1.2.5 - there were no errors with AJAX POST requests in Django 1.2.4 (AJAX wasn't protected in any way, but it worked just fine).

Just like OP, I have tried the JavaScript snippet posted in Django documentation. I'm using jQuery 1.5. I'm also using the "django.middleware.csrf.CsrfViewMiddleware" middleware.

I tried to follow the the middleware code and I know that it fails on this:

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

and then

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

this "if" is true, because "request_csrf_token" is empty.

Basically it means that the header is NOT set. So is there anything wrong with this JS line:

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

?

I hope that provided details will help us in resolving the issue :)

查看更多
登录 后发表回答