django session_key 每次 ajax 调用都不同
django session_key different on each ajax call
我有一个只能通过 AJAX 访问的 Django 应用程序。我的主要问题是我想获得一个唯一 ID,该 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 文档说会话并不总是被创建:
By default, Django only saves to the session database when the session has been modified – that is if any of its dictionary values have been assigned or deleted
https://docs.djangoproject.com/en/1.9/topics/http/sessions/#when-sessions-are-saved
有没有一种方法可以强制创建 django session_key
,即使会话未被修改,但在不应该更改时却不更改?或者有没有办法 "modify the session" 像 Postman 那样正确地创建它?
您的问题基本上来自您请求的 cross-domain 部分。我复制了你的示例,并尝试通过 localhost
访问主页,然后在 localhost
上发送 Ajax
请求,并且 session 密钥被保存。
但是,当我将 Ajax
请求更改为 127.0.0.1
(并正确配置 django-cors-headers
)时,我会遇到与您描述的完全相同的问题. session 键随每个请求而改变。 (CSRF 令牌也是如此,但这是故意的,应该保持这种方式。)
在这里,您混合了 CORS
, third-party 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
,但想法是一样的。)
最后,通过在 INSTALLED_APPS
和 MIDDLEWARE
中添加足够的行来在 settings.py
中允许 CORS
(有关安装的更多信息,请参阅 django-cors-headers ).为方便起见,您允许所有 URL 和所有 ORIGIN
,方法是添加以下行:
# settings.py
CORS_ORIGIN_ALLOW_ALL = True
CORS_URLS_REGEX = True
Do not copy these lines on a production server if you do not understand what it does! It can open a sever security breech.
机制
- 您的浏览器向主页发送一个
GET
请求(假设它是网站的根,请求是GET http://localhost
)
在这里,如果 session 在您的浏览器和 localhost
之间打开并不重要,因为状态可能不会改变。
服务器发回包含 Javascript 代码的 HTML 页面。
您的浏览器解释 Javascript 代码。它创建一个 XMLHttpRequest
,发送到地址 http://127.0.0.1/ajax_call
的服务器。如果一个session之前是用localhost
打开的,这里就不能用了,因为域名不一样。无论如何,没关系,因为在回答这个请求时可以创建session。
服务器收到 127.0.0.1
的请求。如果您没有将此主机添加为 settings.py
中的 allowed-host 并且您 运行 您的服务器处于生产模式,则会引发异常。故事的结局。如果您在 DEBUG
模式下 运行,服务器会创建一个新的 session,因为 none 是由客户端发送的。一切如期进行,一个新的 session_key
和一个新的 CSRF token
被创建,并发送到 header 中的客户端。让我们更准确地看一下。
浏览器发送的header示例如下:
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
,因为没有 session 是打开的。
如果一切顺利,服务器发回的 header 看起来就是这样。
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
服务器创建了一个session,并将所有需要的信息发送给客户端,作为Set-Cookie
header。看起来不错!
- 我们期望浏览器的行为是为域 127.0.0.1 设置 cookie,并将其用于同一域上的下一个请求。但是,查看一个新的请求header显示它没有发送header中的
sessionid
,使得服务器创建一个新的session,发送新的信息作为Set-Cookie
header,重新开始这个过程。如果您查看计算机上存储的 cookie(使用 Firefox,右键单击页面 > View Page Info
,然后选择 Security
选项卡,单击 View Cookies
),您可以看到一些 cookie localhost
,但 127.0.0.1
没有。浏览器不会设置从 Ajax 调用中获取的 cookie。
为什么?
原因太多了!
我们将在解决方案部分列出它们。因为解决这个问题最简单最好的方法不是解锁它,而是正确使用它。
如何解决?
这是第一个、最好、最简单的推荐解决方案:使您的主域和 Ajax 调用域匹配。您将避免 CORS
头痛,避免打开潜在的安全漏洞,不处理 third-party cookie,并使您的开发环境与您的生产环境保持一致。
如果你真的想处理两个不同的域,问问自己是否需要。如果没有,回到1。如果你真的需要,我们来看看解决方案。
- 首先,请确保您的浏览器接受 third-party cookie。对于 Firefox,在
Preferences
中,转到 Privacy
选项卡。在 History
部分,select Firefox will: Use custom settings for history
,并检查 Accept third-party cookies
是否设置为 Always
。如果访问者禁用 third-party cookie,您的网站将因他而损坏。 (转到解决方案 1 的第一个充分理由。)
其次,当收到来自 Ajax 调用的回复时,您必须告诉您的浏览器不要忽略 Cookie 设置。将 withCredentials 设置添加到 xhr
是解决方案。这是新的 Javascript 代码:
function call () {
$.ajax({
type: 'GET',
xhrFields: {
withCredentials: true
},
url: 'http://127.0.0.1/ajax_call/'
}). [...]
如果您现在尝试,您会发现浏览器仍然不满意。原因是
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘http://127.0.0.1/ajax_call/’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’).
这是一个安全功能。使用此配置,您可以再次打开已被 CORS
保护阻止的安全后膛。您允许任何网站发送经过身份验证的 CORS
请求。 永远不要那样做。
使用 CORS_ORIGIN_WHITELIST
而不是 CORS_ORIGIN_ALLOW_ALL
可以解决此问题。
还没有完成。您的浏览器现在抱怨其他事情:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:8000/ajax_call/. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).
同样,这是一项安全功能。通过 CORS
请求发送凭据在大多数情况下 非常糟糕 。您必须在资源端手动允许它。您可以通过激活 CORS_ALLOW_CREDENTIALS
设置来实现。您的 settings.py
文件现在看起来像这样:
CORS_URLS_REGEX = r'.*'
CORS_ORIGIN_WHITELIST = (
'localhost',
'127.0.0.1',
)
CORS_ALLOW_CREDENTIALS = True
现在,您可以再试一次,看看它是否有效,然后再问问自己是否真的需要在 Ajax 调用中使用不同的域。
我有一个只能通过 AJAX 访问的 Django 应用程序。我的主要问题是我想获得一个唯一 ID,该 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 文档说会话并不总是被创建:
By default, Django only saves to the session database when the session has been modified – that is if any of its dictionary values have been assigned or deleted
https://docs.djangoproject.com/en/1.9/topics/http/sessions/#when-sessions-are-saved
有没有一种方法可以强制创建 django session_key
,即使会话未被修改,但在不应该更改时却不更改?或者有没有办法 "modify the session" 像 Postman 那样正确地创建它?
您的问题基本上来自您请求的 cross-domain 部分。我复制了你的示例,并尝试通过 localhost
访问主页,然后在 localhost
上发送 Ajax
请求,并且 session 密钥被保存。
但是,当我将 Ajax
请求更改为 127.0.0.1
(并正确配置 django-cors-headers
)时,我会遇到与您描述的完全相同的问题. session 键随每个请求而改变。 (CSRF 令牌也是如此,但这是故意的,应该保持这种方式。)
在这里,您混合了 CORS
, third-party 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
,但想法是一样的。)
最后,通过在 INSTALLED_APPS
和 MIDDLEWARE
中添加足够的行来在 settings.py
中允许 CORS
(有关安装的更多信息,请参阅 django-cors-headers ).为方便起见,您允许所有 URL 和所有 ORIGIN
,方法是添加以下行:
# settings.py
CORS_ORIGIN_ALLOW_ALL = True
CORS_URLS_REGEX = True
Do not copy these lines on a production server if you do not understand what it does! It can open a sever security breech.
机制
- 您的浏览器向主页发送一个
GET
请求(假设它是网站的根,请求是GET http://localhost
)
在这里,如果 session 在您的浏览器和 localhost
之间打开并不重要,因为状态可能不会改变。
服务器发回包含 Javascript 代码的 HTML 页面。
您的浏览器解释 Javascript 代码。它创建一个
XMLHttpRequest
,发送到地址http://127.0.0.1/ajax_call
的服务器。如果一个session之前是用localhost
打开的,这里就不能用了,因为域名不一样。无论如何,没关系,因为在回答这个请求时可以创建session。服务器收到
127.0.0.1
的请求。如果您没有将此主机添加为settings.py
中的 allowed-host 并且您 运行 您的服务器处于生产模式,则会引发异常。故事的结局。如果您在DEBUG
模式下 运行,服务器会创建一个新的 session,因为 none 是由客户端发送的。一切如期进行,一个新的session_key
和一个新的CSRF token
被创建,并发送到 header 中的客户端。让我们更准确地看一下。
浏览器发送的header示例如下:
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
,因为没有 session 是打开的。
如果一切顺利,服务器发回的 header 看起来就是这样。
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
服务器创建了一个session,并将所有需要的信息发送给客户端,作为Set-Cookie
header。看起来不错!
- 我们期望浏览器的行为是为域 127.0.0.1 设置 cookie,并将其用于同一域上的下一个请求。但是,查看一个新的请求header显示它没有发送header中的
sessionid
,使得服务器创建一个新的session,发送新的信息作为Set-Cookie
header,重新开始这个过程。如果您查看计算机上存储的 cookie(使用 Firefox,右键单击页面 >View Page Info
,然后选择Security
选项卡,单击View Cookies
),您可以看到一些 cookielocalhost
,但127.0.0.1
没有。浏览器不会设置从 Ajax 调用中获取的 cookie。
为什么?
原因太多了! 我们将在解决方案部分列出它们。因为解决这个问题最简单最好的方法不是解锁它,而是正确使用它。
如何解决?
这是第一个、最好、最简单的推荐解决方案:使您的主域和 Ajax 调用域匹配。您将避免
CORS
头痛,避免打开潜在的安全漏洞,不处理 third-party cookie,并使您的开发环境与您的生产环境保持一致。如果你真的想处理两个不同的域,问问自己是否需要。如果没有,回到1。如果你真的需要,我们来看看解决方案。
- 首先,请确保您的浏览器接受 third-party cookie。对于 Firefox,在
Preferences
中,转到Privacy
选项卡。在History
部分,selectFirefox will: Use custom settings for history
,并检查Accept third-party cookies
是否设置为Always
。如果访问者禁用 third-party cookie,您的网站将因他而损坏。 (转到解决方案 1 的第一个充分理由。) 其次,当收到来自 Ajax 调用的回复时,您必须告诉您的浏览器不要忽略 Cookie 设置。将 withCredentials 设置添加到
xhr
是解决方案。这是新的 Javascript 代码:function call () { $.ajax({ type: 'GET', xhrFields: { withCredentials: true }, url: 'http://127.0.0.1/ajax_call/' }). [...]
如果您现在尝试,您会发现浏览器仍然不满意。原因是
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘http://127.0.0.1/ajax_call/’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’).
这是一个安全功能。使用此配置,您可以再次打开已被
CORS
保护阻止的安全后膛。您允许任何网站发送经过身份验证的CORS
请求。 永远不要那样做。 使用CORS_ORIGIN_WHITELIST
而不是CORS_ORIGIN_ALLOW_ALL
可以解决此问题。还没有完成。您的浏览器现在抱怨其他事情:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:8000/ajax_call/. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).
同样,这是一项安全功能。通过
CORS
请求发送凭据在大多数情况下 非常糟糕 。您必须在资源端手动允许它。您可以通过激活CORS_ALLOW_CREDENTIALS
设置来实现。您的settings.py
文件现在看起来像这样:CORS_URLS_REGEX = r'.*' CORS_ORIGIN_WHITELIST = ( 'localhost', '127.0.0.1', ) CORS_ALLOW_CREDENTIALS = True
现在,您可以再试一次,看看它是否有效,然后再问问自己是否真的需要在 Ajax 调用中使用不同的域。
- 首先,请确保您的浏览器接受 third-party cookie。对于 Firefox,在