Django rest 框架使用 httpie 忽略丢失的 csrf 令牌
Django rest framework ignores missing csrf tokens with httpie
我正在使用 httpie 来玩我用 django 1.7 和 django rest framework 2.4 编写的 api。
今天我试图删除一个对象:
$ http DELETE :8000/api/items/8/ --verbose
DELETE /api/items/8/ HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 0
Host: 127.0.0.1:8000
User-Agent: HTTPie/0.8.0
HTTP/1.0 204 NO CONTENT
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Language: cs
Content-Length: 0
Date: Wed, 07 Jan 2015 21:47:06 GMT
Server: WSGIServer/0.1 Python/2.7.6
Vary: Accept, Accept-Language, Cookie
虽然需要 CSRF 令牌,但还是成功了。当我尝试使用以下代码从 Chrome 中删除对象时:
$.ajax({
type: "DELETE",
url: "http://127.0.0.1:8000/api/items/6/"
});
我收到以下请求:
DELETE /api/items/6/ HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
Origin: http://127.0.0.1:8000
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36
DNT: 1
Referer: http://127.0.0.1:8000/inventory
Accept-Encoding: gzip, deflate, sdch
Accept-Language: cs,en-US;q=0.8,en;q=0.6,es;q=0.4,pt;q=0.2,sk;q=0.2
Cookie: cc_csrf=bd9fbbc8f75cffa2e1e3d2c95c2185c5; _ga=GA1.1.2038400685.1386436341; __utma=96992031.2038400685.1386436341.1417173095.1417428975.79; __utmz=96992031.1409752584.3.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __zlcmid=MpdRtV3vZuf3D9; djdt=hide; sessionid=kiihjh6m77jm8v9ol7xrryip89sny55i; csrftoken=FtnnEWPLhMh0CAGMRMH77nB0AAno93uW
回复:
HTTP/1.0 403 FORBIDDEN
Date: Wed, 07 Jan 2015 21:57:40 GMT
Server: WSGIServer/0.1 Python/2.7.6
Vary: Accept, Accept-Language, Cookie
Content-Type: application/json
Content-Language: en
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
{"detail": "CSRF Failed: CSRF token missing or incorrect."}
我的设置:
REST_FRAMEWORK = {
# Use hyperlinked styles by default.
# Only used if the `serializer_class` attribute is not set on a view.
'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.serializers.HyperlinkedModelSerializer',
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
'DATETIME_FORMAT': "%B %d, %Y"
}
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.locale.LocaleMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
所以我的问题是:使用 JS ajax 发送 DELETE 请求和使用 http 发送请求有什么区别?
已编辑和更新
OK 除了其他人已经提到的解释之外,毕竟我们可以得出Httpie允许您删除但不允许您删除的原因Javascript:
1) 由于您实际上已经禁用了您的身份验证,理论上所有的方法都将被允许来自单个 HTTP 调用,因此您的 Httpie 可以工作(就像您使用 Curl 时一样),因为 Restframework 不需要您这样做。
2) Ajax 来自 Javascript 的调用,但略有不同,因为您使用浏览器控制台进行调用,这实际上是在浏览器会话中进行的。除此之外,您的 cookie 已经存储了您之前的 GET 的 CSRF 令牌,之后当您执行 Ajax 调用时,CSRF 令牌已从 Django/Restframework 中提取,这不匹配(因为 CSRF 令牌将自动根据每个请求重新生成。所以这是 INCORRECT 而不是 *MISSING** 令牌的问题。
因此就像我上面的评论一样,删除浏览器的 cookie/使用私人会话确实解决了问题,并成功地允许您执行 Ajax 样式删除。
希望这对您有所帮助,感谢大家的指导和提示让我得出这个结论。
这是因为 CSRF 检查仅在使用 SessionAuthentication
进行身份验证时执行(即使用 django.contrib.auth
设置的 sessionid
cookie):
If you're using SessionAuthentication
you'll need to include valid CSRF tokens for any POST
, PUT
, PATCH
or DELETE
operations. (source)
我假设您对 HTTPie 发送的请求使用了另一种身份验证方法,因此不会在那里应用 CSRF 检查。
https://github.com/tomchristie/django-rest-framework/blob/master/tests/test_authentication.py
当通过浏览器发出请求时,它在 Cookie
header 中包含 sessionid
令牌。 This header is automatically set by the browser,并包括已设置的其他 cookie(如 Django 调试工具栏的 djdt=hide
)。
Cookie: ...; sessionid=kiihjh6m77jm8v9ol7xrryip89sny55i; ...
正因为如此,Django is authenticating the request automatically (just like it normally would), which is triggering the SessionAuthentication
由 Django REST 框架提供。 SessionAuthentication
要求验证 CSRF 令牌,它包含在 csrftoken
cookie 和 X-CSRFToken
header 中,以确保没有任何可疑的事情发生。
这意味着你必须设置 X-CSRFToken
header when making your request in the browser. Django includes some useful snippets of code for popular libraries in their documentation on CSRF.
现在,当通过 HTTPie 发出请求时,您通常使用不同形式的身份验证 such as basic authentication. By default, Django REST framework enables BasicAuthentication
and SessionAuhentication
, unless you override them,并且大多数文档都希望您使用基本身份验证。
HTTPie 通过-a username:password
参数支持基本身份验证。这可以解释为什么您在发出 DELETE
请求时没有遇到任何权限问题,因为如果没有身份验证,您应该会收到 403
错误。 The DjangoModelPermissionsOrAnonReadOnly
should not allow 您可以在未经身份验证的情况下提出您提供的请求。
我正在使用 httpie 来玩我用 django 1.7 和 django rest framework 2.4 编写的 api。 今天我试图删除一个对象:
$ http DELETE :8000/api/items/8/ --verbose
DELETE /api/items/8/ HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, compress
Content-Length: 0
Host: 127.0.0.1:8000
User-Agent: HTTPie/0.8.0
HTTP/1.0 204 NO CONTENT
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Language: cs
Content-Length: 0
Date: Wed, 07 Jan 2015 21:47:06 GMT
Server: WSGIServer/0.1 Python/2.7.6
Vary: Accept, Accept-Language, Cookie
虽然需要 CSRF 令牌,但还是成功了。当我尝试使用以下代码从 Chrome 中删除对象时:
$.ajax({
type: "DELETE",
url: "http://127.0.0.1:8000/api/items/6/"
});
我收到以下请求:
DELETE /api/items/6/ HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
Origin: http://127.0.0.1:8000
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36
DNT: 1
Referer: http://127.0.0.1:8000/inventory
Accept-Encoding: gzip, deflate, sdch
Accept-Language: cs,en-US;q=0.8,en;q=0.6,es;q=0.4,pt;q=0.2,sk;q=0.2
Cookie: cc_csrf=bd9fbbc8f75cffa2e1e3d2c95c2185c5; _ga=GA1.1.2038400685.1386436341; __utma=96992031.2038400685.1386436341.1417173095.1417428975.79; __utmz=96992031.1409752584.3.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __zlcmid=MpdRtV3vZuf3D9; djdt=hide; sessionid=kiihjh6m77jm8v9ol7xrryip89sny55i; csrftoken=FtnnEWPLhMh0CAGMRMH77nB0AAno93uW
回复:
HTTP/1.0 403 FORBIDDEN
Date: Wed, 07 Jan 2015 21:57:40 GMT
Server: WSGIServer/0.1 Python/2.7.6
Vary: Accept, Accept-Language, Cookie
Content-Type: application/json
Content-Language: en
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
{"detail": "CSRF Failed: CSRF token missing or incorrect."}
我的设置:
REST_FRAMEWORK = {
# Use hyperlinked styles by default.
# Only used if the `serializer_class` attribute is not set on a view.
'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.serializers.HyperlinkedModelSerializer',
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
'DATETIME_FORMAT': "%B %d, %Y"
}
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.locale.LocaleMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
所以我的问题是:使用 JS ajax 发送 DELETE 请求和使用 http 发送请求有什么区别?
已编辑和更新
OK 除了其他人已经提到的解释之外,毕竟我们可以得出Httpie允许您删除但不允许您删除的原因Javascript:
1) 由于您实际上已经禁用了您的身份验证,理论上所有的方法都将被允许来自单个 HTTP 调用,因此您的 Httpie 可以工作(就像您使用 Curl 时一样),因为 Restframework 不需要您这样做。
2) Ajax 来自 Javascript 的调用,但略有不同,因为您使用浏览器控制台进行调用,这实际上是在浏览器会话中进行的。除此之外,您的 cookie 已经存储了您之前的 GET 的 CSRF 令牌,之后当您执行 Ajax 调用时,CSRF 令牌已从 Django/Restframework 中提取,这不匹配(因为 CSRF 令牌将自动根据每个请求重新生成。所以这是 INCORRECT 而不是 *MISSING** 令牌的问题。
因此就像我上面的评论一样,删除浏览器的 cookie/使用私人会话确实解决了问题,并成功地允许您执行 Ajax 样式删除。
希望这对您有所帮助,感谢大家的指导和提示让我得出这个结论。
这是因为 CSRF 检查仅在使用 SessionAuthentication
进行身份验证时执行(即使用 django.contrib.auth
设置的 sessionid
cookie):
If you're using
SessionAuthentication
you'll need to include valid CSRF tokens for anyPOST
,PUT
,PATCH
orDELETE
operations. (source)
我假设您对 HTTPie 发送的请求使用了另一种身份验证方法,因此不会在那里应用 CSRF 检查。
https://github.com/tomchristie/django-rest-framework/blob/master/tests/test_authentication.py
当通过浏览器发出请求时,它在 Cookie
header 中包含 sessionid
令牌。 This header is automatically set by the browser,并包括已设置的其他 cookie(如 Django 调试工具栏的 djdt=hide
)。
Cookie: ...; sessionid=kiihjh6m77jm8v9ol7xrryip89sny55i; ...
正因为如此,Django is authenticating the request automatically (just like it normally would), which is triggering the SessionAuthentication
由 Django REST 框架提供。 SessionAuthentication
要求验证 CSRF 令牌,它包含在 csrftoken
cookie 和 X-CSRFToken
header 中,以确保没有任何可疑的事情发生。
这意味着你必须设置 X-CSRFToken
header when making your request in the browser. Django includes some useful snippets of code for popular libraries in their documentation on CSRF.
现在,当通过 HTTPie 发出请求时,您通常使用不同形式的身份验证 such as basic authentication. By default, Django REST framework enables BasicAuthentication
and SessionAuhentication
, unless you override them,并且大多数文档都希望您使用基本身份验证。
HTTPie 通过-a username:password
参数支持基本身份验证。这可以解释为什么您在发出 DELETE
请求时没有遇到任何权限问题,因为如果没有身份验证,您应该会收到 403
错误。 The DjangoModelPermissionsOrAnonReadOnly
should not allow 您可以在未经身份验证的情况下提出您提供的请求。