【问题标题】:Django rest framework ignores missing csrf tokens with httpieDjango rest 框架使用 httpie 忽略缺少的 csrf 令牌
【发布时间】:2015-03-05 22:26:54
【问题描述】:

我正在使用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 发送请求有什么区别?

【问题讨论】:

  • 您问题中的 httpie 命令是否与您正在使用的完全相同?还是您在某处传递凭据?如果是匿名的,该请求应该会失败。
  • @VisgeanSkeloru,您可以尝试删除您的 Javascript 的 cookie 或尝试使用 private 模式再次发送 ajax 吗?
  • @VisgeanSkeloru,我已经相应地更新了我的答案,很抱歉我在与其他成员讨论前后方向错误,并重新研究您的请求/响应机构,我意识到我错过了要点,这与您的 missing CSRF 令牌无关,而是关于 incorrect 令牌

标签: python django rest django-rest-framework


【解决方案1】:

这是因为 CSRF 检查仅在使用SessionAuthentication 进行身份验证时执行,(即使用django.contrib.auth 设置的sessionid cookie):

如果您使用SessionAuthentication,则需要为任何POSTPUTPATCHDELETE 操作包含有效的CSRF 令牌。 (source)

我假设您对 HTTPie 发送的请求使用了另一种身份验证方法,因此 CSRF 检查不会在那里应用。

https://github.com/tomchristie/django-rest-framework/blob/master/tests/test_authentication.py

【讨论】:

  • 这是问题所在,出于测试目的,我在这个单一的休息视图中关闭了身份验证。
【解决方案2】:

当通过浏览器发出请求时,它会在Cookie 标头中包含sessionid 令牌。 This header is automatically set by the browser,并包括其他已设置的 cookie(如 Django 调试工具栏的 djdt=hide)。

Cookie: ...; sessionid=kiihjh6m77jm8v9ol7xrryip89sny55i; ...

因此,Django is authenticating the request automatically(就像平常一样),即 Django REST 框架提供的triggering the SessionAuthenticationSessionAuthentication 要求验证 CSRF 令牌,该令牌包含在 csrftoken cookie 和 X-CSRFToken 标头中,以确保没有发生任何可疑事件。

这意味着您必须设置X-CSRFToken 标头when making your request in the browser。 Django 在their documentation on CSRF 中包含一些用于流行库的有用的sn-ps 代码。

现在,当通过 HTTPie 发出请求时,您通常使用不同形式的身份验证 such as basic authentication。默认情况下,Django REST 框架启用BasicAuthenticationSessionAuhenticationunless you override them,并且大多数文档都希望您使用基本身份验证。

HTTPie 通过-a username:password 参数支持基本身份验证。这可以解释为什么您在发出DELETE 请求时没有遇到任何权限问题,因为如果没有身份验证,您应该会收到403 错误。 The DjangoModelPermissionsOrAnonReadOnly should not allow您可以在未经身份验证的情况下提出您提供的请求。

【讨论】:

    【解决方案3】:

    编辑和更新

    好吧,除了其他人已经提到的解释,毕竟我们可以总结出Httpie允许你的DELETE而不是Javascript的原因:

    1) 由于您实际上已禁用身份验证,理论上所有方法都将允许来自单个 HTTP 调用,因此您的 Httpie 可以正常工作(就像您使用 Curl 时一样),因为 Restframework 不需要您这样做。

    2) 来自 Javascript 的 Ajax 调用略有不同,因为您使用浏览器控制台进行调用,这实际上是在浏览器会话中。此外,您的 cookie 存储了您之前的 GET 的 CSRF 令牌,之后当您执行 Ajax 调用时,CSRF 令牌已从不匹配的 Django/Restframework 中提取(因为 CSRF 令牌将在每个请求上自动重新生成. 所以这是 INCORRECT 而不是 *MISSING** 令牌的问题。

    因此,就像我上面的评论一样,删除浏览器的 cookie / 使用私有会话确实解决了问题,并成功地允许您执行 Ajax 样式的 DELETE。

    希望这会有所帮助,并感谢大家的指导和提示,使我得出这个结论。

    【讨论】:

    • 出于演示目的,我删除了身份验证...我有点困惑:http 首先从哪里获取 csrf cookie?如果是单个请求,则服务器和 httpie 之间没有事先通信。也不会出现在http请求中吗?我指定了详细参数,没有提到csrf。
    • @VisgenSkeloru,你能显示完整的请求和响应正文吗?我需要检查文档,但我认为如果没有找到以前的会话,httpie 会在帖子之前模拟一个获取。
    • 完整的请求和响应在我原来的问题中。没有获取请求 - 我会在服务器日志中看到。
    • @VisgeanSkeloru,您是否将您的 httpie 设置为持久化,或者您之前是否对该站点进行过任何 GET/POST 等调用?当您从同一主机地址拨打电话时,会话将被存储。
    • 我做了一些获取请求,但没有使用持久性 cookie。我删除了 .httpie/sessions 但它的行为仍然相同。
    猜你喜欢
    • 1970-01-01
    • 2015-02-02
    • 1970-01-01
    • 1970-01-01
    • 2017-10-02
    • 2021-05-20
    • 1970-01-01
    • 2017-12-19
    • 2019-07-28
    相关资源
    最近更新 更多