Не вдалося перевірити Django CSRF із запитом Ajax POST


180

Я міг би скористатись допомогою, що відповідає механізму захисту Джанго 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викликом, і він справді заповнений деякими даними. Я не впевнений, як перевірити, що маркер правильний, але мені сподобається, що він щось знаходить і надсилає.

Але Джанго досі відкидає мій пост у AJAX.

Ось мій JavaScript:

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

Ось помилка, яку я бачу від Django:

[23 / лют / 2011 22:08:29] "POST / запам'ятати / HTTP / 1.1" 403 2332

Я впевнений, що мені щось не вистачає, і, можливо, це просто, але я не знаю, що це. Я обшукав SO і побачив деяку інформацію про вимкнення перевірки CSRF на мій погляд через csrf_exemptдекоратор, але вважаю це непривабливим. Я спробував це, і це працює, але я б швидше змусив свою пошту працювати так, як Джанго був розроблений, щоб очікувати цього, якщо це можливо.

На випадок, якщо це корисно, ось суть мого погляду:

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
Яку версію джанго ви використовуєте?
zsquare

Ви додали до правильних класів середнього програмного забезпечення CSRF і розмістили їх у правильному порядку?
darren

Якуб відповів на моє запитання нижче, але про всяк випадок, якщо це корисно іншим людям: @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, офіційна документація про джанго працювала для мене.
monkut

1
Я намагався , але це не схоже на роботу для мене, я використовую JQuery v1.7.2, моє запитання stackoverflow.com/questions/11812694 / ...
мрійник

Я повинен додати анотацію @ensure_csrf_cookie до функції перегляду, щоб змусити встановити файл cookie csrf, коли сторінку запитують з мобільних пристроїв.
Кейн

172

Якщо ви використовуєте $.ajaxфункцію, ви можете просто додати csrfмаркер в тіло даних:

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

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

і зробили.


Я спробував це, за винятком того, що моя форма має завантаження файлу. Мій бекенд - це джанго і досі отримую помилку 400CSRF Failed: CSRF token missing or incorrect.
Хуссейн

16

Проблема полягає в тому, що django очікує, що значення від файлу cookie буде передано назад як частина даних форми. Код з попередньої відповіді отримує javascript для пошуку значень файлів cookie та введення їх у дані форми. Це прекрасний спосіб зробити це з технічної точки зору, але це виглядає трохи багатослівно.

Раніше я це робив простіше, отримавши javascript для введення значення маркера в дані публікації.

Якщо ви використовуєте {% csrf_token%} у своєму шаблоні, ви отримаєте приховане поле форми, яке несе значення. Але якщо ви використовуєте {{csrf_token}}, ви просто отримаєте оголене значення токена, тож ви можете використовувати це у javascript, як це ....

csrf_token = "{{ csrf_token }}";

Потім ви можете включити це з необхідним ім'ям ключа в хеш, який ви подаєте в якості даних для виклику ajax.


@aehlke Ви можете мати статичні файли. У вихідному коді ви можете побачити хороший приклад, коли ви реєструєте змінні django в windowоб'єкті, щоб вони були доступні згодом. Навіть у статичних файлах.
KitKat

3
@KitKat дійсно :) Вибачте за мої давні, неосвічені коментарі. Гарна думка.
aehlke

повторні статичні файли. Не проблема, якщо ви не заперечуєте проти крихітного js свого html. Я просто помістив {{csrf_token}} в головний HTML-шаблон, недалеко від вимог вимог. працював як шарм.
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);
});

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.


5

Використовуйте Firefox з Firebug. Відкрийте вкладку "Консоль" під час запуску запиту на ajax. Коли DEBUG=Trueви отримаєте приємну сторінку помилки django як відповідь, і ви навіть можете побачити виведений html відповіді ajax на вкладці консолі.

Тоді ви дізнаєтеся, в чому помилка.


5

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

Я зіткнувся з цією проблемою на Django 1.3, і це було викликано тим, що файли cookie CSRF не були встановлені в першу чергу. Django не встановлюватиме файли cookie, якщо цього не доведеться. Тож ексклюзивно або сильно 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/# аякс


Моя відповідь була точною на момент її написання :) Це було лише після того, як Джанго було оновлено з 1.2.4 до 1.2.5. Було також, коли новітня версія jQuery становила 1,5. Виявляється, джерелом проблеми була помилка jQuery (1.5), і ця інформація тепер додається до Django doc, як ви вже заявляли. У моєму випадку: cookie WAS встановлено, а маркер НЕ додано до запиту AJAX. Дане виправлення працювало на помилку jQuery 1.5. На сьогоднішній день ви можете просто дотримуватися офіційних документів, використовуючи наведений там приклад коду та використовуючи новітні jQuery. У вашої проблеми було інше джерело, ніж тут обговорювались проблеми :)
Якуб Гоцлавський

2
Зараз існує декоратор, ensure_csrf_cookieякий можна назвати навколо подання, щоб переконатися, що він надсилає файли cookie.
Брайан Ніл

Це питання, яке у мене виникло, csrftokenв першу чергу немає файлу cookie, дякую!
кроди

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

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's шаблонна система з тегом '{% csrf_token%}'. Я не пробував на старшій Джанго, просто трапився і вирішив на Джанго1.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

Це буде працювати, навіть якщо ви не застосовуєте форми джанго.

Насправді логіка тут полягає в тому, що вам потрібно ознайомитись із запитом. Тому вам просто потрібно з'ясувати функцію, яка викликається відразу після входу в систему. Як тільки ви отримаєте цей маркер, або зробіть інший 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, або в режимі джанго.

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;

0

Ось менш детальне рішення, яке надає Джанго:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.