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

137

Я могу использовать некоторую помощь, связанную с механизмом защиты Django CSRF, через мой пост 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.");
    }
});

Здесь ошибка, которую я вижу из Django:

[23/Feb/2011 22:08:29] "POST/запомнить/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))

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

  • 1
    Какую версию Django вы используете?
  • 0
    Вы добавили правильные классы промежуточного программного обеспечения CSRF и разместили их в правильном порядке?
Показать ещё 1 комментарий
Теги:
csrf

16 ответов

153
Лучший ответ

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

Хорошо, мне удалось проследить проблему. Он находится в коде 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'));
         }
     } 
});

вместо кода, опубликованного в официальных документах: http://docs.djangoproject.com/en/1.2/ref/contrib/csrf/#ajax

Рабочий код происходит из этой записи Django: http://www.djangoproject.com/weblog/2011/feb/08/security/

Итак, общее решение: "используйте обработчик ajaxSetup вместо обработчика ajaxSend". Я не знаю, почему это работает. Но это работает для меня:)

Предыдущее сообщение (без ответа)

На самом деле у меня такая же проблема.

Это происходит после обновления до Django 1.2.5 - ошибок в AJAX POST-запросах в Django 1.2.4 не было (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'));

?

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

  • 0
    Это сработало! Я вставил функцию .ajaxSetup, как вы вставили ее выше, и теперь я могу отправлять сообщения без ошибки 403. Спасибо, что поделились решением, Якуб. Хорошая находка. :)
  • 0
    Использование ajaxSetup вместо ajaxSend противоречит документам jQuery: api.jquery.com/jQuery.ajaxSetup
Показать ещё 5 комментариев
122

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

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },
  • 2
    когда я использую помеченный ответ, он работает для меня, но если я использую ваше решение, это не так. Но ваше решение должно работать, я не понимаю, почему это не так. Что еще нужно сделать в Django 1.4?
  • 1
    Спасибо! так просто. Все еще работает на django 1.8 и jquery 2.1.3
Показать ещё 3 комментария
65

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

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

и сделано.

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

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

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

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

csrf_token = "{{ csrf_token }}";

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

  • 4
    Не очень хорошее решение, так как это означает, что у вас не может быть статических файлов .js ...
  • 0
    @aehlke Вы можете иметь статические файлы. В исходном коде вы можете увидеть хороший пример, где вы регистрируете переменные django в объекте window , чтобы они были доступны впоследствии. Даже в статических файлах.
Показать ещё 2 комментария
10

{% 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);
});
7

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

5

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

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

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

Edit

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

  • 0
    Мой ответ был точным на момент написания этого :) Это было сразу после того, как Django был обновлен с 1.2.4 до 1.2.5. Это было также, когда новейшая версия jQuery была 1.5. Оказывается, источником проблемы была ошибка jQuery (1.5), и эта информация теперь добавляется в Django doc, как вы заявили. В моем случае: файл cookie был установлен, и токен НЕ был добавлен в запрос AJAX. Данное исправление сработало для прослушиваемого jQuery 1.5. На данный момент вы можете просто придерживаться официальных документов, используя приведенный там пример кода и используя новейший jQuery. Ваша проблема имела другой источник, чем проблемы, обсуждаемые здесь :)
  • 2
    Теперь существует декоратор под названием ensure_csrf_cookie , который можно обернуть вокруг представления, чтобы убедиться, что он отправляет cookie.
Показать ещё 1 комментарий
4

Не-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);
4

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

Затем вы узнаете, что такое ошибка.

2

Я только что столкнулся с другой, но схожей ситуацией. Не 100% уверены, что это будет решение вашего дела, но я решил проблему для Django 1.3, установив параметр POST "csrfmiddlewaretoken" с правильной строкой значений cookie, которая обычно возвращается в форме вашего домашнего HTML Django шаблона с тегом '{% csrf_token%}'. Я не пробовал старшего Django, просто случилось и разрешилось на Django1.3. Моя проблема заключалась в том, что первый запрос, представленный через Ajax из формы, был успешно выполнен, но вторая попытка от того же самого из неудачного привела к состоянию 403, даже если заголовок X-CSRFToken правильно помещен также с токеном CSRF как в случае первой попытки. Надеюсь, это поможет.

Привет,

Хиро

1

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

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

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

  • 0
    Не очень хорошо структурировано, но хорошо объяснено. Моя проблема была в том, что я отправлял csrf следующим образом: csrftoken: csrftoken , а не csrfmiddlwaretoken: csrftoken . После смены все заработало. Спасибо
0

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

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

Если кто-то борется с аксиомами, чтобы сделать эту работу, это помогло мне:

import axios from 'axios';

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

Источник: https://cbuelter.wordpress.com/2017/04/10/django-csrf-with-axios/

0

для тех, кто сталкивается с этим и пытается отлаживать:

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 правильно получен из запроса. META

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

0

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

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

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;

Ещё вопросы

Сообщество Overcoder
Наверх
Меню