Сбой проверки CSRF в Django с помощью запроса Ajax POST - PullRequest
169 голосов
/ 24 февраля 2011

Я мог бы помочь с соблюдением механизма защиты CSRF в Django через мой пост AJAX. Я следовал указаниям здесь:

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

Вот ошибка, которую я вижу от Джанго:

[23 февраля 2011 г. 22:08:29] "POST / memorize / HTTP / 1.1" 403 2332

Я уверен, что что-то упустил, и, возможно, это просто, но я не знаю, что это. Я искал вокруг SO и увидел некоторую информацию об отключении проверки CSRF для моего просмотра через декоратор csrf_exempt, но я нахожу это непривлекательным. Я попробовал это, и это сработало, но я бы предпочел, чтобы мой 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))

Спасибо за ваши ответы!

Ответы [ 18 ]

172 голосов
/ 24 февраля 2011

Реальное решение

Хорошо, мне удалось отследить проблему. Он лежит в коде 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, я попробовал фрагмент JavaScript, размещенный в документации Django. Я использую 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)

это "если" верно, потому что "request_csrf_token" пусто.

В основном это означает, что заголовок НЕ установлен. Так что с этой строкой JS что-то не так:

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

Я надеюсь, что предоставленные данные помогут нам решить проблему:)

146 голосов
/ 30 мая 2011

Если вы используете функцию $.ajax, вы можете просто добавить токен csrf в теле данных:

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },
72 голосов
/ 10 октября 2011

Добавьте эту строку в свой код jQuery:

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

и готово.

15 голосов
/ 27 февраля 2011

Проблема в том, что django ожидает, что значение из cookie будет передано обратно как часть данных формы. Код из предыдущего ответа заставляет javascript выявлять значение cookie и помещать его в данные формы. Это прекрасный способ сделать это с технической точки зрения, но он выглядит немного многословно.

В прошлом я делал это проще, получая JavaScript, чтобы поместить значение токена в данные поста.

Если вы используете {% csrf_token%} в своем шаблоне, вы получите скрытое поле формы, содержащее значение. Но, если вы используете {{csrf_token}}, вы просто получите чистое значение токена, так что вы можете использовать его в javascript следующим образом ....

csrf_token = "{{ csrf_token }}";

Затем вы можете включить это с требуемым именем ключа в хеш, который вы затем отправляете как данные для вызова ajax.

13 голосов
/ 25 ноября 2014

{% csrf_token %}, помещенный в HTML-шаблоны внутри <form></form>

переводится в нечто вроде:

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

так почему бы просто не выполнить поиск в вашем JS следующим образом:

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

, а затем передать его, например, сделать POST, например:

$.post( "/panel/change_password/", {foo: bar, csrfmiddlewaretoken: token}, function(data){
    console.log(data);
});
8 голосов
/ 13 сентября 2011

Если ваша форма правильно публикуется в Django без JS, вы сможете постепенно расширять ее с помощью 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.

7 голосов
/ 16 августа 2014

Ответ без запроса:

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);
5 голосов
/ 24 августа 2011

Принято, скорее всего, красная сельдь. Разница между Django 1.2.4 и 1.2.5 заключалась в требовании токена CSRF для запросов AJAX.

Я столкнулся с этой проблемой на Django 1.3, и она была , вызванная тем, что файл cookie CSRF не был установлен в первую очередь. Django не будет устанавливать куки, если это не нужно. Таким образом, сайт исключительно или с большим количеством ajax, работающий на Django 1.2.4, потенциально никогда не отправит токен клиенту, и тогда обновление, требующее токен, вызовет ошибки 403.

Идеальное решение здесь: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
но вам придется подождать 1.4, если это не просто документация, догоняющая код

Редактировать

Обратите внимание, что в более поздних документах Django отмечена ошибка в jQuery 1.5, поэтому убедитесь, что вы используете 1.5.1 или более позднюю версию с предложенным Django кодом: http://docs.djangoproject.com/en/1.3/ref/contrib/csrf/#ajax

4 голосов
/ 24 февраля 2011

Используйте Firefox с Firebug.Откройте вкладку «Консоль» во время запуска AJAX-запроса.С DEBUG=True вы получите красивую страницу с ошибкой django в качестве ответа и даже сможете увидеть визуализированный html-ответ ajax на вкладке консоли.

Тогда вы узнаете, что это за ошибка.

4 голосов
/ 08 октября 2017

Кажется, никто не упомянул, как сделать это в чистом JS, используя заголовок X-CSRFToken и {{ csrf_token }}, поэтому вот простое решение, в котором вам не нужно искать файлы cookie или DOM:

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