Django CSRF检查失败,出现Ajax POST请求

时间:2011-02-24 04:58:12

标签: python ajax django csrf

我可以通过我的AJAX帖子使用一些符合Django CSRF保护机制的帮助。我遵循了这里的指示:

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

感谢您的回复!

22 个答案:

答案 0 :(得分:168)

真正的解决方案

好的,我设法追查问题了。它位于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一样,我尝试过在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'));

我希望提供的详细信息有助于我们解决问题:)

答案 1 :(得分:146)

如果您使用$.ajax功能,只需在数据正文中添加csrf令牌即可:

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

答案 2 :(得分:72)

将此行添加到您的jQuery代码中:

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

并完成。

答案 3 :(得分:14)

问题是因为django期望cookie的值作为表单数据的一部分传回。上一个答案中的代码是使用javascript来查找cookie值并将其放入表单数据中。从技术的角度来看,这是一种可爱的方式,但看起来确实有些冗长。

过去,我通过让javascript将令牌值放入帖子数据中来做得更简单。

如果您在模板中使用{%csrf_token%},您将获得一个带有值的隐藏表单字段。但是,如果你使用{{csrf_token}},你将只获得令牌的裸值,所以你可以在这样的javascript中使用它....

csrf_token = "{{ csrf_token }}";

然后,您可以在哈希中包含所需的密钥名称,然后将其作为数据提交给ajax调用。

答案 4 :(得分:13)

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

答案 5 :(得分:8)

如果您的表单在没有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应用程序。

答案 6 :(得分:7)

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

答案 7 :(得分:5)

接受的答案很可能是红鲱鱼。 Django 1.2.4和1.2.5之间的区别是对AJAX请求的CSRF令牌的要求。

我在Django 1.3上遇到过这个问题,并且首先由于没有设置CSRF cookie而导致 。 Django除非必须,否则不会设置cookie。因此,在Django 1.2.4上运行的独占或大量的ajax站点可能永远不会向客户端发送令牌,然后需要令牌的升级将导致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/#ajax

答案 8 :(得分:4)

将Firefox与Firebug一起使用。在触发ajax请求时打开“控制台”选项卡。使用DEBUG=True,您可以获得漂亮的django错误页面作为响应,您甚至可以在控制台选项卡中看到呈现的ajax响应的html。

然后你会知道错误是什么。

答案 9 :(得分:3)

似乎没有人提到如何使用X-CSRFToken标头和{{ csrf_token }}在纯JS中执行此操作,因此这里是一个简单的解决方案,您无需搜索Cookie或DOM:

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

答案 10 :(得分:2)

我刚刚遇到了一些不同但相似的情况。不是100%肯定它是否是你的案例的解决方案,但我通过设置一个POST参数'csrfmiddlewaretoken'与正确的cookie值字符串解决了Django 1.3的问题,该字符串通常由您的家庭HTML形式由Django的表单返回带有'{%csrf_token%}'标签的模板系统。我没有尝试使用较旧的Django,只是发生了并在Django1.3上解决了。 我的问题是,从表单中通过Ajax提交的第一个请求已成功完成,但第二次尝试从完全相同的失败,导致403状态,即使标题'X-CSRFToken'正确放置了CSRF令牌值也是如此就像第一次尝试的情况一样。 希望这会有所帮助。

此致

答案 11 :(得分: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>

答案 12 :(得分:1)

使用Django轻松进行ajax调用

(2020年10月26日)
在我看来,这比正确的答案要简洁得多。

视图

@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's documentation中找到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添加到当前页面

这可能是个话题,但我很少见到使用它,这是一种最小化窗口重定位以及在javascript中手动创建html字符串的好方法。

这与上面的非常相似,但是这次我们从响应中渲染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 %}

答案 13 :(得分:1)

如果有人和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/

答案 14 :(得分:1)

对于遇到此问题且正在尝试调试的人:

1)django csrf检查(假设您正在发送)是here

2)在我的情况下,settings.CSRF_HEADER_NAME设置为&#39; HTTP_X_CSRFTOKEN&#39;我的AJAX调用发送了一个名为&#39; HTTP_X_CSRF_TOKEN&#39;所以东西不起作用。我可以在AJAX调用或django设置中更改它。

3)如果您选择在服务器端进行更改,请找到django的安装位置并在csrf middleware中抛出一个断点。如果您正在使用virtualenv,那么&#39;我会像:~/.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, '')

然后,确保{request {USD}

正确地获取csrf令牌

4)如果您需要更改标题等 - 在设置文件中更改该变量

答案 15 :(得分:1)

为每个会话分配一个CSRF令牌(即每次登录时)。 因此,在您希望获取用户输入的某些数据并将其作为ajax调用发送到受csrf_protect装饰器保护的某个函数之前,请尝试在从用户获取此数据之前查找正在调用的函数。例如。必须呈现某些模板,用户在该模板上输入数据。 该模板由某些函数呈现。 在此函数中,您可以获取csrf令牌,如下所示: csrf = request.COOKIES [&#39; csrftoken&#39;] 现在在上下文字典中传递此csrf值,以便对正在呈现的模板进行渲染。 现在在该模板中写下这一行: 现在在你的javascript函数中,在发出ajax请求之前,写下:  var csrf = $(&#39;#csrf&#39;)。val()这将选择传递给模板的令牌值并将其存储在变量csrf中。 现在在进行ajax调用时,在你的帖子数据中,也传递这个值: &#34; csrfmiddlewaretoken&#34;:csrf

即使您没有实现django表单,这也会有效。

事实上,这里的逻辑是:你需要你可以从请求获得的令牌。 所以你只需要在登录后立即找出被调用的函数。一旦你有了这个令牌,要么再做一个ajax调用来获取它,或者把它传递给你的ajax可以访问的模板。

答案 16 :(得分:0)

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

答案 17 :(得分:0)

由于当前答案中未在任何地方说明,因此,如果您未将 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!");
            }
        })

答案 18 :(得分:0)

chosen Answer相关,只想添加到所选答案中。

在该答案中,关于使用.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/

(我没有足够的代表来评论,所以我将输入的内容发布为答案)

答案 19 :(得分:0)

我有解决办法。在我的JS中,我有两个功能。 首先获得Cookies(即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);
}

答案 20 :(得分:0)

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

答案 21 :(得分:0)

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

我必须在配置中注释掉这两行以使其再次起作用:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;