Сбой проверки CSRF в Django с помощью POST-запроса Ajax

180

Я мог бы использовать некоторую помощь в соответствии с механизмом защиты 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 / Feb / 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))

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

firebush
источник
1
Какую версию Django вы используете?
zsquare
Вы добавили правильные классы промежуточного программного обеспечения CSRF и разместили их в правильном порядке?
Даррен
Якуб ответил на мой вопрос ниже, но на всякий случай он пригодится другим людям: @zsquare: версия 1.2.3. @mongoose_za: Да, они добавлены и в правильном порядке.
Firebush

Ответы:

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, я попробовал фрагмент 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'));

?

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

Якуб Гоклавский
источник
Это сработало! Я вставил функцию .ajaxSetup, как вы вставили ее выше, и теперь я могу писать без ошибки 403. Спасибо, что поделились решением, Якуб. Хорошая находка. :)
firebush
Использование, ajaxSetupа не ajaxSendпротиворечит документам jQuery: api.jquery.com/jQuery.ajaxSetup
Марк Лавин,
используя 1.3, официальная запись документации django работала на меня.
monkut
1
Я пытался, но это не работает для меня, я использую jQuery v1.7.2, мой вопрос: stackoverflow.com/questions/11812694/…
daydreamer
Я должен добавить аннотацию @ensure_csrf_cookie к своей функции просмотра, чтобы принудительно установить cookie-файл csrf при запросе страницы с мобильных устройств.
Кейн
172

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

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },
Bryan
источник
2
когда я использую отмеченный ответ, он работает для меня, но если я использую ваше решение здесь, это не так. Но ваше решение должно работать, я не понимаю, почему это не так. Что еще нужно сделать в Django 1.4?
Хуман
1
Спасибо! так просто. Все еще работает над django 1.8 и jquery 2.1.3
Alejandro Veintimilla
19
Это решение требует, чтобы JavaScript был встроен в шаблон, не так ли?
Мокс
15
@Mox: Поместите это в html, но над вашим Js-файлом, где находится функция ajax <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>
HereHere
Спасибо! Так просто и элегантно. У меня работает с Django 1.8. Я добавил csrfmiddlewaretoken: '{{ csrf_token }}'в свой dataсловарь в $.postвызове.
Павел Юдаев
75

Добавьте эту строку в ваш код JQuery:

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

и сделано.

Камбиз
источник
Я попробовал это, за исключением того, что в моей форме есть файл для загрузки. Мой бэкэнд - django и все еще получает ошибку 400CSRF Failed: CSRF token missing or incorrect.
Хуссейн
16

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

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

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

csrf_token = "{{ csrf_token }}";

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

fatgeekuk
источник
@aehlke Вы можете иметь статические файлы. В исходном коде вы можете увидеть хороший пример, где вы регистрируете переменные django в windowобъекте, чтобы впоследствии они были доступны. Даже в статических файлах.
KitKat
3
@KitKat действительно :) Извините за мой древний, невежественный комментарий здесь. Хорошая точка зрения.
aehlke
повторно статические файлы. Не проблема, если вы не возражаете против чуть-чуть JS вашего HTML. Я просто поместил {{csrf_token}} в основной шаблон HTML, недалеко от заклинаний requirejs. работал как шарм.
JL Peyret
14

{% 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);
});
andilabs
источник
11

Ответ не на вопросы:

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

Если ваша форма правильно публикуется в 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.

GivP
источник
5

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

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

jammon
источник
5

Принято, скорее всего, красная сельдь. Разница между 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/# Аякса

Стивен
источник
Мой ответ был точным на момент написания этого :) Это было сразу после того, как Django был обновлен с 1.2.4 до 1.2.5. Это было также, когда новейшая версия jQuery была 1.5. Оказывается, источником проблемы была ошибка jQuery (1.5), и эта информация теперь добавляется в Django doc, как вы заявили. В моем случае: файл cookie был установлен, и токен НЕ был добавлен в запрос AJAX. Данное исправление сработало для прослушиваемого jQuery 1.5. На данный момент вы можете просто придерживаться официальных документов, используя приведенный там пример кода и используя новейший jQuery. У вашей проблемы был источник, отличный от обсуждаемых здесь :)
Jakub Gocławski
2
Теперь существует декоратор ensure_csrf_cookie, который можно обернуть вокруг представления, чтобы убедиться, что он отправляет cookie.
Брайан Нил
Это проблема, с которой я csrftokenстолкнулся, во-первых, нет файлов cookie, спасибо!
crhodes
5

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

var xhttp = new XMLHttpRequest();
xhttp.open("POST", url, true);
xhttp.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
xhttp.send();
Alex
источник
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%, будет ли это решением для вашего случая, но я решил проблему для Django 1.3, установив параметр POST 'csrfmiddlewaretoken' с правильной строкой значения cookie, которая обычно возвращается в виде вашего домашнего HTML Django система шаблонов с тегом {% 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>

nooobpan
источник
1

Один токен CSRF присваивается каждому сеансу (т. Е. При каждом входе в систему). Поэтому, прежде чем вы захотите получить некоторые данные, введенные пользователем, и отправить их как вызов ajax какой-либо функции, защищенной декоратором csrf_protect, попробуйте найти функции, которые вызываются, прежде чем вы получите эти данные от пользователя. Например, должен отображаться шаблон, по которому ваш пользователь вводит данные. Этот шаблон обрабатывается какой-то функцией. В этой функции вы можете получить токен csrf следующим образом: csrf = request.COOKIES ['csrftoken'] Теперь передайте это значение csrf в контекстном словаре, для которого отображается соответствующий шаблон. Теперь в этом шаблоне напишите следующую строку: Теперь в вашей функции javascript перед выполнением ajax-запроса напишите это: var csrf = $ ('# csrf'). val () это выберет значение токена, переданного в шаблон, и сохранит его в переменной csrf. Теперь во время вызова ajax в ваших данных поста также передайте это значение: "csrfmiddlewaretoken": csrf

Это будет работать, даже если вы не используете формы django.

Фактически, логика здесь такова: вам нужен токен, который вы можете получить из запроса. Поэтому вам просто нужно выяснить, какая функция вызывается сразу после входа в систему. Получив этот токен, либо сделайте еще один вызов ajax, чтобы получить его, либо передайте его какому-нибудь шаблону, доступному вашему ajax.

AMIT PRAKASH PANDEY
источник
Не очень хорошо структурировано, но хорошо объяснено. Моя проблема была в том, что я отправлял 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токен правильно получен из запроса.

4) Если вам нужно изменить заголовок и т. Д., Измените эту переменную в файле настроек

daino3
источник
0

В моем случае проблема была в конфигурации nginx, которую я скопировал с основного сервера на временный с отключением https, который не нужен на втором процессе.

Мне пришлось закомментировать эти две строки в конфиге, чтобы он снова заработал:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;
int_ua
источник
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/

Брэден Холт
источник