django session_key在每个ajax调用上都不同

时间:2016-06-08 19:44:45

标签: jquery ajax django django-sessions

我有一个django应用程序,我只通过AJAX访问。我的主要问题是我想获得一个唯一的ID,它与发出请求的特定浏览器实例配对。

为了尝试这样做,我尝试访问django创建的session_key,但它有时会以None的形式返回。

以下是我在django中创建JSON响应的方式:

def get(self, request, pk, format=None):
    resp_obj = {}
    ...
    resp_obj['csrftoken'] = csrftoken
    # shouldn't need the next two lines, but request.session.session_key is None sometimes
    if not request.session.exists(request.session.session_key):
        request.session.create()
    resp_obj['sessionid'] = request.session.session_key
    return JSONResponse(resp_obj)

当我使用Postman发出请求时,session_key在JSON主体和cookie中都会出现,但是当我在浏览器中通过jquery发出请求时,request.session.session_key是None,这就是我添加这些内容的原因:

if not request.session.exists(request.session.session_key):
    request.session.create()

但是当我这样做时,session_key每次都不同。

以下是我如何进行AJAX通话:

for (var i = 0; i < this.survey_ids.length; i++) {
  $.ajax({
    url: this.SERVER_URL+ '/surveys/' + this.survey_ids[i] + '/?language=' + VTA.user_language,
    headers: {
      'Accept-Language': user_language
    }
  }).error(function (jqXHR, textStatus, errorThrown) {
    // handle the error
  }).done(function (response, textStatus, jqXHR) {

    window.console.log(response.csrftoken)  // different on each iteration
    window.console.log(response.sessionid)  // also different on each iteration

    //handle response

  })
}

Django文档说并不总是创建会话:

  

默认情况下,Django仅在修改会话时保存到会话数据库 - 即,如果已分配或删除任何字典值

     

https://docs.djangoproject.com/en/1.9/topics/http/sessions/#when-sessions-are-saved

有没有办法强制django session_key创建即使会话没有被修改,但是当它不应该被修改时也没有改变?或者有没有办法来修改会话&#34;这样它就像Postman一样正确地创建了吗?

1 个答案:

答案 0 :(得分:2)

您的问题主要来自您请求的跨域部分。我复制了您的示例,并尝试使用localhost访问主页,然后在Ajax上发送localhost请求,并保存会话密钥。

但是,当我将Ajax请求更改为127.0.0.1时(并使用django-cors-headers的正确配置),我可以获得与您描述的完全相同的问题。会话密钥针对每个请求进行更改。 (CSRF令牌也是如此,但这是故意的,应该保持这种方式。)

在这里,您混合了CORS,第三方cookiesXMLHttpRequest中的凭据,这使整个事情破裂。

到底发生了什么?

上下文

首先,让我们就您的文件内容达成一致。

# views.py
from django.http import JsonResponse
from django.middleware import csrf

def ajax_call(request):
    if not request.session.exists(request.session.session_key):
        request.session.create()
    # To debug the server side
    print request.session.session_key
    print csrf.get_token(request)

    # To debug the client side
    resp_obj = {}
    resp_obj['sessionid'] = request.session.session_key
    resp_obj['csrf'] = csrf.get_token(request)
    return JsonResponse(resp_obj)

较大HTML页面中包含的Javascript代码:

# Javascript, client side
function call () {
    $.ajax({
        type: 'GET',
        xhrFields: {
            withCredentials: true
        },
        url: 'http://127.0.0.1/ajax_call/'
    }).fail(function () {
        console.log("Error");
    }).done(function (response, textStatus, jqXHR) {
        console.log("Success");
        console.log(response.sessionid);
        console.log(response.csrf);
    })
}

请注意,Ajax请求是在127.0.0.1上发出的,而不是在localhost上,因为它是您的情况。 (或者,您可以访问127.0.0.1上的主HTML页面并在Ajax调用中使用localhost,但想法是一样的。)

最后,通过在CORSsettings.py中添加足够的行,允许在INSTALLED_APPSMIDDLEWARE {有关安装的更多信息,请参阅django-cors-headers。为方便起见,您可以通过添加以下行来允许所有网址和所有ORIGIN

# settings.py
CORS_ORIGIN_ALLOW_ALL = True
CORS_URLS_REGEX = True
  

如果您不理解它的作用,请不要在生产服务器上复制这些行!它可以打开一个严重的安全后膛。

机制

  1. 您的浏览器向主页发送GET请求(让我们想象它是网站的根目录,请求是GET http://localhost
  2. 此处,如果会话在您的浏览器与localhost之间打开,则无关紧要,因为状态可能不会发生变化。

    服务器发回包含Javascript代码的HTML页面。

    1. 您的浏览器会解释Javascript代码。它会创建一个XMLHttpRequest,并将其发送到地址http://127.0.0.1/ajax_call的服务器。如果会话之前已使用localhost打开,则此处无法使用,因为域名不同。无论如何,这并不重要,因为在回答此请求时可以创建会话。

    2. 服务器收到127.0.0.1的请求。如果您未在settings.py中将此主机添加为允许主机,并且您在生产模式下运行服务器,则会引发异常。故事的结局。如果您以DEBUG模式运行,则服务器会创建一个新会话,因为客户端没有发送任何会话。一切都按预期进行,创建了新的session_key和新的CSRF token,并在标头中发送给客户端。让我们更准确地看一下。

    3. 以下是浏览器发送的标头示例:

      Host: localhost
      User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
      Accept: */*
      Accept-Language: en-US,en;q=0.5
      Accept-Encoding: gzip, deflate
      Referer: http://localhost/
      X-Requested-With: XMLHttpRequest
      Cookie: csrftoken=XCadvu4MJjDMzCkOTTC276oJs9P0j989CBw6rnidz7cS34PoOt1VftqWMqd8BHMX; django_language=en
      DNT: 1
      Connection: keep-alive
      

      仔细查看Cookie行。没有发送sessionid,因为没有会话打开。

      如果一切顺利,服务器发回的标题就是这样。

      Content-Length: 125
      Content-Type: application/json
      Date: Sun, 17 Sep 2017 14:21:23 GMT
      Server: WSGIServer/0.1 Python/2.7.14rc1
      Set-Cookie: csrftoken=XCadvu4MJjDMzCkOTTC276oJs9P0j989CBw6rnidz7cS34PoOt1VftqWMqd8BHMX; expires=Sun, 16-Sep-2018 14:21:22 GMT; Max-Age=31449600; Path=/
      sessionid=l505q4y8pywe9t3q76204662a1225scx; expires=Sun, 01-Oct-2017 14:21:22 GMT; httponly; Max-Age=1209600; Path=/
      Vary: Cookie
      x-frame-options: SAMEORIGIN
      

      服务器创建了一个会话,并将所有需要的信息作为Set-Cookie标头发送到客户端。看起来不错!

      1. 我们期望从浏览器中获得的行为是它为域127.0.0.1设置cookie,并将其用于同一域上的下一个请求。但是,查看新的请求标头会显示它未在标头中发送sessionid,使服务器创建新会话,将新信息作为Set-Cookie标头发送,然后重新启动该过程。如果您查看计算机上存储的cookie(使用Firefox,右键单击页面&gt; View Page Info,然后点击Security标签,点击View Cookies),您可以看到一些Cookie localhost,但127.0.0.1没有任何内容。浏览器不会设置从Ajax调用中获取的cookie。
      2. 为什么?

        出于这么多原因! 我们将在解决方案部分列出所有这些内容。因为最简单和最好的解决方法不是解锁它,而是正确使用它。

        如何解决?

        1. 以下是第一个,最好,最简单的推荐解决方案:使您的主域和Ajax呼叫域匹配。您将避免CORS头痛,避免打开潜在的安全后膛,不处理第三方cookie,并使您的开发环境与您的生产保持一致。

        2. 如果您真的想要处理两个不同的域,请问问自己是否需要它。如果你没有,请回到1.如果你真的需要它,那就让我们检查解决方案。

          • 首先,请确保您的浏览器接受第三方Cookie。对于Firefox,在Preferences中,转到Privacy标签。在History部分,选择Firefox will: Use custom settings for history,然后检查Accept third-party cookies是否设置为Always。如果访问者禁用第三方Cookie,您的网站将会被破坏。 (第一个很好的理由去解决方案1.)
          • 其次,在收到Ajax调用的回复时,您必须告诉浏览器不要忽略Cookie设置。向xhr添加withCredentials设置是解决方案。这是新的Javascript代码:

            function call () {
                $.ajax({
                    type: 'GET',
                    xhrFields: {
                        withCredentials: true
                    },
                    url: 'http://127.0.0.1/ajax_call/'
                }). [...]
            
          • 如果你现在尝试,你会发现浏览器仍然不满意。原因是

              

            阻止跨源请求:同源策略禁止在“http://127.0.0.1/ajax_call/”处读取远程资源。 (原因:如果CORS标题'Access-Control-Allow-Origin'为'*',则不支持凭证)。

            这是一项安全功能。使用此配置,您再次打开已被CORS保护阻止的安全后膛。您允许任何网站发送经过身份验证的CORS请求。 永远不要那样做。 使用CORS_ORIGIN_WHITELIST代替CORS_ORIGIN_ALLOW_ALL修复此问题。

          • 还没有完成。您的浏览器现在会抱怨其他内容:

              

            阻止跨源请求:同源策略禁止在http://127.0.0.1:8000/ajax_call/读取远程资源。 (原因:在CORS标题'Access-Control-Allow-Credentials'中预期为'true')。

            同样,这是一项安全功能。通过CORS请求发送凭据大部分时间非常糟糕。您必须在资源端手动允许它。您可以通过激活CORS_ALLOW_CREDENTIALS设置来执行此操作。您的settings.py文件现在看起来像这样:

            CORS_URLS_REGEX = r'.*'
            CORS_ORIGIN_WHITELIST = (
                'localhost',
                '127.0.0.1',
            )
            CORS_ALLOW_CREDENTIALS = True
            
          • 现在,您可以再试一次,看看它是否有效,并再次问问自己是否真的需要在Ajax调用中使用不同的域。