jQuery AJAX POST 在第一次调用时跳过 Django CSRF 令牌标头

Posted

技术标签:

【中文标题】jQuery AJAX POST 在第一次调用时跳过 Django CSRF 令牌标头【英文标题】:jQuery AJAX POST skips Django CSRF token header on first call 【发布时间】:2011-10-19 07:23:34 【问题描述】:

使用 jQuery AJAX POST 调用 Django 以获取更新的 JSON 集。

在我的 JS 文件中包含 Django 应用程序的“django.middleware.csrf.CsrfViewMiddleware”和“django.middleware.csrf.CsrfResponseMiddleware”以及来自https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax 的代码。

$(document).ajaxSend(function(event, 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;
    
    function sameOrigin(url) 
    // url could be relative or scheme relative or absolute
    var host = document.location.host; // host + port
    var protocol = document.location.protocol;
    var sr_origin = '//' + host;
    var origin = protocol + sr_origin;
    // Allow absolute or scheme relative URLs to same origin
    return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
        (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
        // or any other URL that isn't scheme relative or absolute i.e relative.
        !(/^(\/\/|http:|https:).*/.test(url));
    
    function safeMethod(method) 
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    

    if (!safeMethod(settings.type) && sameOrigin(settings.url)) 
    xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
    
);

会发生什么

    清除浏览器 cookie 并加载页面。 Django 以 403 禁止响应 AJAX POST 请求,然后使用 CSRF 设置一个 cookie。但是没有 JSON 数据。 再次触发 AJAX POST 请求 Django 从 X-CSRFToken 标头接收 CSRF 令牌,并使用 JSON 集进行适当的响应。

产生 ajax 请求的 html 和 js 文件不是 django 产生的。 js 通过 jquery $.post() 与 django 交互。所以获取 csrf 令牌的唯一方法是在发送 post 数据之前调用 django。

在我看来,Django 文档中给出的 jQuery 函数解决方案还没有本地 cookie,并且在提交 POST 请求之前没有等待从服务器取回 cookie。但是当它与 403 一起使用时,它使用(现在本地存储的)cookie 进行后续尝试并且工作正常。

如何解决此问题,以便提前设置 cookie 并在 AJAX POST 请求中第一次使用该 cookie?我是否错误地认为 django csrf 可以严格从源自同一站点的 ajax 调用工作?


*此处与 CSRF 有关的其他问题根本不起作用,建议使用 ajax.Setup 而不是 ajaxSend,但根据此 https://code.djangoproject.com/ticket/15284?cversion=0&cnum_hist=6 ,这可能会导致其他 JS 出现问题,因此选择了 ajaxSend。*

【问题讨论】:

【参考方案1】:

看起来中间件类的放置顺序错误。要获得正确答案需要更多信息,请在 settings.py 中提供 MIDDLEWARE_CLASSES。

将中间件“django.middleware.csrf.CsrfViewMiddleware”添加到您的 中间件类列表,MIDDLEWARE_CLASSES。 (它应该来了 在任何假设 CSRF 攻击已经被攻击的视图中间件之前 处理。

或者,您可以使用装饰器 csrf_protect() 您想要保护的视图(见下文)。

来源:How to use CSRF protection

【讨论】:

MIDDLEWARE_CLASSES = ('django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfResponseMiddleware', 'django.contrib.sessions.middleware. SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ) 从 django 的角度来看,csrf 中间件似乎工作正常,因为第一个 ajax 请求从 django 获取令牌,第二个请求使用令牌并且 django 很高兴。 实际上CSRF cookie必须在第一步设置,当页面加载时 包含 AJAX 的页面不是在 Django 中构建的。它是直接的 HTML(尽管我在 Django 中重建它来处理这个问题和其他问题)虽然据我了解,这不重要。 AJAX 调用应该能够在没有任何整页请求的情况下获取和读取 Django 提供的 cookie。似乎脚本在发送发布数据之前没有从远程获取 cookie。它只是在寻找一个在第一次请求时还不存在的本地 cookie。似乎需要的是在将 POST 数据发送到 Django 之前使 CSRF 值成为必需 这是有道理的,因为 Django 正是从那种请求中提供了安全性。当 Django 返回使用 ajax 调用的页面的响应时,必须设置 cookie。虽然您可以使用@csrf_exempt 来禁用对处理 AJAX 的视图的 csrf 检查,或者将直接的 HTML 页面实现到视图中。

以上是关于jQuery AJAX POST 在第一次调用时跳过 Django CSRF 令牌标头的主要内容,如果未能解决你的问题,请参考以下文章

jquery ajax'post'调用

重写ajax方法实现异步请求session过期时跳转登录页面

从 jQuery.post AJAX 调用返回数据?

jQuery Ajax Post 更改对象结构

对 ASP.NET Web 服务的 jQuery ajax POST 调用随机失败

使用 POST 方法从 jQuery Ajax 调用 WCF 服务