我有一个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一样正确地创建了吗?
答案 0 :(得分:2)
您的问题主要来自您请求的跨域部分。我复制了您的示例,并尝试使用localhost
访问主页,然后在Ajax
上发送localhost
请求,并保存会话密钥。
但是,当我将Ajax
请求更改为127.0.0.1
时(并使用django-cors-headers
的正确配置),我可以获得与您描述的完全相同的问题。会话密钥针对每个请求进行更改。 (CSRF令牌也是如此,但这是故意的,应该保持这种方式。)
在这里,您混合了CORS
,第三方cookies和XMLHttpRequest
中的凭据,这使整个事情破裂。
首先,让我们就您的文件内容达成一致。
# 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
,但想法是一样的。)
最后,通过在CORS
和settings.py
中添加足够的行,允许在INSTALLED_APPS
中MIDDLEWARE
{有关安装的更多信息,请参阅django-cors-headers。为方便起见,您可以通过添加以下行来允许所有网址和所有ORIGIN
:
# settings.py
CORS_ORIGIN_ALLOW_ALL = True
CORS_URLS_REGEX = True
如果您不理解它的作用,请不要在生产服务器上复制这些行!它可以打开一个严重的安全后膛。
GET
请求(让我们想象它是网站的根目录,请求是GET http://localhost
)此处,如果会话在您的浏览器与localhost
之间打开,则无关紧要,因为状态可能不会发生变化。
服务器发回包含Javascript代码的HTML页面。
您的浏览器会解释Javascript代码。它会创建一个XMLHttpRequest
,并将其发送到地址http://127.0.0.1/ajax_call
的服务器。如果会话之前已使用localhost
打开,则此处无法使用,因为域名不同。无论如何,这并不重要,因为在回答此请求时可以创建会话。
服务器收到127.0.0.1
的请求。如果您未在settings.py
中将此主机添加为允许主机,并且您在生产模式下运行服务器,则会引发异常。故事的结局。如果您以DEBUG
模式运行,则服务器会创建一个新会话,因为客户端没有发送任何会话。一切都按预期进行,创建了新的session_key
和新的CSRF token
,并在标头中发送给客户端。让我们更准确地看一下。
以下是浏览器发送的标头示例:
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
标头发送到客户端。看起来不错!
sessionid
,使服务器创建新会话,将新信息作为Set-Cookie
标头发送,然后重新启动该过程。如果您查看计算机上存储的cookie(使用Firefox,右键单击页面&gt; View Page Info
,然后点击Security
标签,点击View Cookies
),您可以看到一些Cookie localhost
,但127.0.0.1
没有任何内容。浏览器不会设置从Ajax调用中获取的cookie。出于这么多原因! 我们将在解决方案部分列出所有这些内容。因为最简单和最好的解决方法不是解锁它,而是正确使用它。
以下是第一个,最好,最简单的推荐解决方案:使您的主域和Ajax呼叫域匹配。您将避免CORS
头痛,避免打开潜在的安全后膛,不处理第三方cookie,并使您的开发环境与您的生产保持一致。
如果您真的想要处理两个不同的域,请问问自己是否需要它。如果你没有,请回到1.如果你真的需要它,那就让我们检查解决方案。
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调用中使用不同的域。