Сбой проверки 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>
У меня есть решение. в моем 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/
(У меня недостаточно комментариев для комментариев, поэтому я отправляю свой ответ в виде ответа)