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

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

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

Я скопировал пример кода AJAX, который есть на этой странице:

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

Я поставил оповещение с печатью содержимого 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))

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

23 ответа

Решение

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

Хорошо, мне удалось отследить проблему. Он лежит в коде 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/

Рабочий код взят из этой записи 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'));

?

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

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

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

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

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

и сделано.

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

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

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

csrf_token = "{{ csrf_token }}";

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

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

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

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

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

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

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

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

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

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

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

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

Поскольку в текущих ответах это нигде не указано, самое быстрое решение, если вы не встраиваете 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!");
            }
        })

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

С Уважением,

Хиро

Обновление 2022

В атаке CSRF злоумышленник обманом заставляет невиновного конечного пользователя отправить веб-запрос, который он не собирался

Опция 1

      from django.views.decorators.csrf import csrf_exempt
from django.http.response import JsonResponse


@csrf_exempt
def commentDeletePost(request):
    if request.is_ajax() and request.method == 'POST':
        try:
            comment = Comment.objects.get(pk=request.POST['pk'])
            if comment.author != request.user:
                return JsonResponse({'e': 'Forbidden'}, status=403) 
            comment.delete()
            return JsonResponse({}, status=200)
        execpt Comment.DoesNotExist:
            return JsonResponse({'e': 'Not Found'}, status=404)

вариант 2

      <div id="csrf">
    {% csrf_token %}
</div>
<script type="text/javascript">
    window.crud = {
        commentDelete: function(
            pk, 
            success,
            error, 
        ){
            $.ajax({
                headers: {'X-CSRFToken': document.getElementById('csrf').querySelector('input').value},
                type: "POST", 
                url: "{% url 'comment-delete-post' %}",
                data: {
                    pk: pk,
                }, 
                success: success, 
                error: error,
            })
        }, 
    }
</script>

Простые вызовы ajax с Django

(26.10.2020)
На мой взгляд, это намного проще и понятнее, чем правильный ответ.

Вид

@login_required
def some_view(request):
    """Returns a json response to an ajax call. (request.user is available in view)"""
    # Fetch the attributes from the request body
    data_attribute = request.GET.get('some_attribute')  # Make sure to use POST/GET correctly
    # DO SOMETHING...
    return JsonResponse(data={}, status=200)

urls.py

urlpatterns = [
    path('some-view-does-something/', views.some_view, name='doing-something'),
]

Вызов ajax

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

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

$.ajax({
    url: "{% url 'doing-something' %}",
    headers: {'X-CSRFToken': csrftoken},
    data: {'some_attribute': some_value},
    type: "GET",
    dataType: 'json',
    success: function (data) {
        if (data) {
            console.log(data);
            // call function to do something with data
            process_data_function(data);
        }
    }
});

Добавить HTML на текущую страницу с помощью ajax

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

Это очень похоже на приведенное выше, но на этот раз мы визуализируем html из ответа без перезагрузки текущего окна.

Если вы намеревались отобразить какой-то HTML-код из данных, которые вы получите в ответ на вызов ajax, возможно, будет проще отправить HttpResponse обратно из представления вместо JsonResponse. Это позволяет легко создавать HTML, который затем можно вставить в элемент.

Вид

# The login required part is of course optional
@login_required
def create_some_html(request):
    """In this particular example we are filtering some model by a constraint sent in by 
    ajax and creating html to send back for those models who match the search"""
    # Fetch the attributes from the request body (sent in ajax data)
    search_input = request.GET.get('search_input')

    # Get some data that we want to render to the template
    if search_input:
        data = MyModel.objects.filter(name__contains=search_input) # Example
    else:
        data = []

    # Creating an html string using template and some data
    html_response = render_to_string('path/to/creation_template.html', context = {'models': data})

    return HttpResponse(html_response, status=200)

Шаблон создания html для просмотра

creation_template.html

{% for model in models %}
   <li class="xyz">{{ model.name }}</li>
{% endfor %}

urls.py

urlpatterns = [
    path('get-html/', views.create_some_html, name='get-html'),
]

Основной шаблон и вызов ajax

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

{% extends 'base.html' %}
{% load static %}
{% block content %}
    <input id="search-input" placeholder="Type something..." value="">
    <button id="add-html-button" class="btn btn-primary">Add Html</button>
    <ul id="add-html-here">
        <!-- This is where we want to render new html -->
    </ul>
{% end block %}

{% block extra_js %}
    <script>
        // When button is pressed fetch inner html of ul
        $("#add-html-button").on('click', function (e){
            e.preventDefault();
            let search_input = $('#search-input').val();
            let target_element = $('#add-html-here');
            $.ajax({
                url: "{% url 'get-html' %}",
                headers: {'X-CSRFToken': csrftoken},
                data: {'search_input': search_input},
                type: "GET",
                dataType: 'html',
                success: function (data) {
                    if (data) {
                        /* You could also use json here to get multiple html to
                        render in different places */
                        console.log(data);
                        // Add the http response to element
                        target_element.html(data);
                    }
                }
            });
        })
    </script>
{% endblock %}

Вы можете вставить этот 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>

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

import axios from 'axios';

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

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

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

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) Если вам нужно изменить заголовок и т. Д., Измените эту переменную в файле настроек

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

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

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

Использование Django 3.1.1 и всех решений, которые я пробовал, не удалось. Однако добавление ключа csrfmiddlewaretoken к моему телу POST сработало. Вот мой звонок:

$.post(url, {
  csrfmiddlewaretoken: window.CSRF_TOKEN,
  method: "POST",
  data: JSON.stringify(data),
  dataType: 'JSON',
});

И в шаблоне HTML:

<script type="text/javascript">
  window.CSRF_TOKEN = "{{ csrf_token }}";
</script>

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

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

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;

Вот менее подробное решение, предоставленное 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/

У меня есть решение. в моем JS у меня есть две функции. Сначала получите файлы cookie (например, csrftoken):

function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
    const cookies = document.cookie.split(';');
    for (let i = 0; i < cookies.length; i++) {
        const cookie = cookies[i].trim();
        // 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;

}

Второй - моя функция ajax. в данном случае это для входа в систему и фактически ничего не возвращает, просто передайте значения для установки сеанса:

function LoginAjax() {


    //get scrftoken:
    const csrftoken = getCookie('csrftoken');

    var req = new XMLHttpRequest();
    var userName = document.getElementById("Login-Username");
    var password = document.getElementById("Login-Password");

    req.onreadystatechange = function () {
        if (this.readyState == 4 && this.status == 200) {            
            //read response loggedIn JSON show me if user logged in or not
            var respond = JSON.parse(this.responseText);            
            alert(respond.loggedIn);

        }
    }

    req.open("POST", "login", true);

    //following header set scrftoken to resolve problem
    req.setRequestHeader('X-CSRFToken', csrftoken);

    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.send("UserName=" + userName.value + "&Password=" + password.value);
}

Связанный с выбранным ответом, просто хочу добавить к выбранному ответу.

В этом ответе относительно решения с .ajaxSetup(...). В вашем Django settings.py, если у вас есть

CSRF_USE_SESSIONS = True

Это приведет к тому, что выбранный ответ вообще не будет работать. Удаление этой строки или установка для нее значения False сработали для меня при реализации выбранного решения Answer.

Интересно, что если вы установите следующее в своем Django settings.py

CSRF_COOKIE_HTTPONLY = True

Эта переменная не приведет к прекращению работы выбранного решения ответа.

И то и другое CSRF_USE_SESSIONS а также CSRF_COOKIE_HTTPONLYвзято из этого официального документа Django https://docs.djangoproject.com/en/2.2/ref/csrf/

(У меня недостаточно комментариев для комментариев, поэтому я отправляю свой ответ в виде ответа)

Другие вопросы по тегам