Django CSRF检查Ajax POST请求失败

通过我的AJAX帖子,我可以使用一些帮助来遵守Django的CSRF保护机制。我遵循了这里的说明:

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

我已经复制了他们在该页面上的AJAX示例代码:

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

我在xhr.setRequestHeader调用之前放置了一个警告,打印getCookie('csrftoken')的内容,它确实填充了一些数据。我不确定如何验证令牌是正确的,但我被鼓励它正在寻找和发送一些东西。

但是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 / remember / HTTP/1.1" 403 2332

我肯定我遗漏了什么,也许很简单,但我不知道是什么。我在SO周围搜索过,看到了一些关于通过csrf_exempt装饰器关闭视图的CSRF检查的信息,但我发现那没有吸引力。我已经尝试过了,它是有效的,但如果可能的话,我宁愿让我的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))

谢谢你的回复!

200797 次浏览

使用Firefox和Firebug。在触发ajax请求时打开“Console”选项卡。使用DEBUG=True,你可以得到django错误页面作为响应,你甚至可以在控制台选项卡中看到ajax响应的渲染html。

然后你就知道误差是什么了。

真正的解决方案

好的,我设法找到了问题所在。它存在于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/ < / p >

工作代码来自这个Django条目:http://www.djangoproject.com/weblog/2011/feb/08/security/

所以一般的解决方案是:“使用ajaxSetup处理程序而不是ajaxSend处理程序”。我不知道为什么会这样。但这对我来说很管用:)

以前的帖子(没有回答)

其实我也遇到了同样的问题。

它发生在更新到Django 1.2.5之后——在Django 1.2.4中AJAX POST请求没有错误(AJAX没有受到任何方式的保护,但它工作得很好)。

就像OP一样,我尝试了Django文档中发布的JavaScript代码片段。我使用的是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)

这个“if”为真,因为“request_csrf_token”为空。

基本上这意味着头文件没有被设置。所以这JS行有什么问题吗:

xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));

?

我希望提供的细节将有助于我们解决这个问题:)

这个问题是因为django希望cookie中的值作为表单数据的一部分传递回来。前一个答案中的代码是让javascript寻找cookie值并将其放入表单数据中。从技术角度来看,这是一种可爱的方式,但它看起来有点啰嗦。

在过去,我通过让javascript将令牌值放入post数据中更简单地做到了这一点。

如果在模板中使用{% csrf_token %},则会发出一个包含该值的隐藏表单字段。但是,如果你使用\{\{csrf_token}},你只会得到令牌的裸值,所以你可以在javascript中使用这个....

csrf_token = "\{\{ csrf_token }}";

然后,可以在散列中包含所需的键名,然后将其作为数据提交给ajax调用。

我刚刚遇到了一个有点不同但相似的情况。不是100%确定这是否能解决你的问题,但我解决了Django 1.3的问题,通过设置一个POST参数'csrfmiddlewaretoken'与适当的cookie值字符串,通常由Django的模板系统返回在你的主页HTML的形式与'{% csrf_token %}'标签。我并没有尝试旧版本的Django,而是选择了Django1.3。 我的问题是,通过Ajax从表单提交的第一个请求成功完成,但第二次尝试从完全相同的失败,导致403状态,即使标头“X-CSRFToken”被正确地放置与CSRF令牌值以及在第一次尝试的情况下。

问候,

如果使用$.ajax函数,可以简单地在数据体中添加csrf令牌:

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

公认的答案很可能是在转移注意力。Django 1.2.4和1.2.5之间的区别在于AJAX请求需要CSRF令牌。

我在Django 1.3上遇到了这个问题,它首先是导致CSRF cookie未设置。除非必须,否则Django不会设置cookie。因此,在Django 1.2.4上运行的纯ajax或重度ajax站点可能永远不会向客户端发送令牌,然后需要令牌的升级将导致403错误。

理想的解决方案是: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
但您必须等待1.4,除非这只是文档追赶代码

<强>编辑< / >强

还需要注意的是,Django文档在jQuery 1.5中指出了一个bug,所以请确保您使用的是1.5.1或更高版本的Django建议代码: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax < / p >

如果你的表单在不使用JS的情况下可以在Django中正确地发布,你应该可以使用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应用程序。

将这一行添加到jQuery代码中:

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

和完成。

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

{% csrf_token %}<form></form>中放入html模板

翻译过来就是:

<input type='hidden' name='csrfmiddlewaretoken' value='Sdgrw2HfynbFgPcZ5sjaoAI5zsMZ4wZR' />

所以为什么不像这样在你的JS中grep它:

token = $("#change_password-form").find('input[name=csrfmiddlewaretoken]').val()

然后传递它,例如做一些POST,比如:

$.post( "/panel/change_password/", {foo: bar, csrfmiddlewaretoken: token}, function(data){
console.log(data);
});

在我的情况下,问题是与nginx配置,我已经从主服务器复制到一个临时禁用https,在第二个过程中不需要。

我不得不注释掉配置中的这两行,以使它再次工作:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;
一个CSRF令牌分配给每个会话(即每次登录)。 因此,在你想获取user输入的数据并将其作为ajax调用发送给某个受csrf_protect decorator保护的函数之前,试着在从user获取数据之前找到被调用的函数。例如,某些模板必须被呈现,你的用户正在输入数据。 该模板正由某个函数呈现。 在这个函数中,你可以得到csrf token,如下所示: 请求。饼干(“csrftoken”) 现在,将这个csrf值传递到上下文字典中,根据该上下文字典呈现所讨论的模板。 现在在模板中写这一行: 现在在你的javascript函数中,在发出ajax请求之前,这样写: Var CSRF = $('# CSRF ').val()将选择传递给模板的令牌值并将其存储在变量CSRF中。 现在,当进行ajax调用时,在你的post数据中,传递这个值: "csrfmiddlewaretoken": CSRF

.

这将工作,即使你没有实现django表单。

事实上,这里的逻辑是:你需要token,你可以从request中获得。 因此,您只需要在登录后立即找出被调用的函数。一旦你有了这个令牌,要么进行另一个ajax调用来获取它,要么将它传递给一些可以通过你的ajax访问的模板

你可以把这个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) django的csrf检查(假设你正在发送一个)是在这里

2)在我的情况下,settings.CSRF_HEADER_NAME被设置为“HTTP_X_CSRFTOKEN”,我的AJAX调用正在发送一个名为“HTTP_X_CSRF_TOKEN”的头,所以东西不工作。我可以改变它在AJAX调用,或django设置。

3)如果你选择更改它的服务器端,找到django的安装位置,并在csrf middleware中抛出一个断点。如果你正在使用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)如果你需要改变你的头,等等-改变你的设置文件中的变量

如果有人正在与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/

似乎没有人提到过如何在纯JS中使用X-CSRFToken报头和\{\{ csrf_token }}来做到这一点,所以这里有一个简单的解决方案,你不需要搜索cookie或DOM:

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

下面是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到你的模板中,最快的解决方案是:

<script type="text/javascript"> window.CSRF_TOKEN = "\{\{ csrf_token }}"; </script>放在模板中script.js文件的引用之前,然后将csrfmiddlewaretoken添加到js文件中的data字典中:

$.ajax({
type: 'POST',
url: somepathname + "do_it/",
data: {csrfmiddlewaretoken: window.CSRF_TOKEN},
success: function() {
console.log("Success!");
}
})

选择答案相关,只是想添加到选定的答案。

在这个答案中,关于.ajaxSetup(...)的解决方案。在Django settings.py中,如果你有

CSRF_USE_SESSIONS = True

这将导致所选的答案根本不起作用。在实现所选答案的解决方案时,删除该行或将其设置为False对我有用。

有趣的是,如果你在Django settings.py中设置了以下内容

CSRF_COOKIE_HTTPONLY = True

此变量不会导致所选答案的解决方案停止工作。

CSRF_USE_SESSIONSCSRF_COOKIE_HTTPONLY都来自这个官方的Django文档https://docs.djangoproject.com/en/2.2/ref/csrf/

(我没有足够的代表来评论,所以我张贴我的输入一个答案)

我有一个解决方案。在我的JS我有两个函数。 首先得到饼干(即。csrftoken): < / p >
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);
}

使用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>

使用Django轻松调用ajax

< p > (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调用非常简单,但对于大多数情况来说已经足够了。您可以获取一些值并将它们放入数据对象中,然后在上面描述的视图中,您可以通过它们的名称再次获取它们的值。

你可以在django的文档中找到csrftoken函数。基本上只是复制它,并确保在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);
}
}
});

使用ajax将HTML添加到当前页面

这可能有点离题,但我很少看到这个使用,这是一个伟大的方式来减少窗口重定位以及手动html字符串创建javascript。

这与上面的非常相似,但这一次我们从响应中呈现html,而不需要重新加载当前窗口。

如果您打算从作为ajax调用响应接收到的数据中呈现某种html,那么从视图返回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 %}

更新2022

在CSRF攻击中,无辜的终端用户被攻击者欺骗,提交了一个他们不打算提交的web请求

选项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>

两个选项各有优点。第一个选项将丢弃csrf令牌,这将不会保护您的站点免受csrf攻击,但它将允许用户使用相同的Ajax功能发送多个请求。 第二个选项将限制用户只发送一个Ajax请求,因为csrf令牌只能使用一次,但它更安全。我个人更喜欢选项1,因为Ajax函数如like, star, unlike需要多个Ajax调用,并且允许用户调用多次并不是一个有风险的函数