CSRF 和 CORS 与 Django(REST 框架)

Posted

技术标签:

【中文标题】CSRF 和 CORS 与 Django(REST 框架)【英文标题】:CSRF and CORS with Django (REST Framework) 【发布时间】:2018-08-20 18:14:06 【问题描述】:

我们正在将前端移动到一个单独的项目中(从 Django 中移出)。这是一个 javascript 单页应用程序。

其中一个原因是为了让我们的前端开发人员更轻松地完成他们的工作,而不必在本地运行整个项目(包括 API)。相反,我们希望他们能够与我们设置的测试 API 进行通信。

在此过程中,我们已经设法解决了大部分 CORS/CSRF 问题。但是现在我们遇到了一些我无法在任何地方找到解决方案的问题,尽管阅读了大量文档和 SO 答案。

前端和 API 来自不同的域(在开发期间 localhosttest-api.example.com)。到目前为止,虽然从同一个域提供服务,但前端已经能够从 API (Django) 设置的 csrftoken cookie 中获取 CSRF 令牌。但是当从不同的域提供服务时,前端 (localhost) 无法访问 API (api-test.example.com) 的 cookie。

我正在尝试找出解决此问题的方法,以某种方式将 CSRF 令牌传递到前端。 The Django docs recommend to set a custom X-CSRFToken header 用于 AJAX 请求。如果我们在每个响应中类似地将 CSRF 令牌作为标头提供并且(通过Access-Control-Expose-Headers)允许前端读取此标头,我们会损害 CSRF 保护吗?

鉴于我们已经为 API 正确设置了 CORS(即只允许某些域对 API 进行跨源请求),第 3 方站点上的 JS 应该无法读取此响应标头,因此无法在我们的用户背后发出妥协的 AJAX 请求,对吗?还是我错过了一些重要的事情?

或者还有其他更好的方法来实现我们想要的吗?

【问题讨论】:

【参考方案1】:

一开始我没有理解你的问题,所以让我总结一下:你无法从客户端上的 cookie 中获取 CSRF 令牌,因为同源策略阻止你访问跨域 cookie(即使使用 CORS )。因此,您建议服务器改为在自定义标头中将 cookie 传输给客户端,并且想知道这是否安全。

现在,如果您不使用 cookie,文档确实对如何传输令牌提出了建议:put it in the response body。例如,您可以使用自定义 meta 标记。在安全方面,我倾向于使用推荐的解决方案,而不是相信我自己对新事物的分析。

除此之外,我认为您的建议没有任何安全问题。同源策略将阻止第三方站点像读取正文一样读取标头,您可以选择使用 CORS Access-Control-Expose-Headers 标头从您的客户端域读取它们。

您可能会发现this answer 很有趣,因为它列出了各种 CSRF 令牌方案的优缺点。它包括使用自定义响应标头,并且(就您的问题而言)确认:“如果恶意用户尝试以上述任何方法读取用户的 CSRF 令牌,那么同源策略将阻止这种情况” .

(您可能想了解您的 SPA 是否需要 Django 的 CSRF 保护。例如,请参阅 this analysis。不过,这超出了本问题的范围。)

【讨论】:

我没有误解文档 :) 我的全部观点是:response 标头是发送令牌的安全方式 other way 作为那么,从服务器到客户端?由于跨域限制,前端无法从 cookie 中读取令牌。这就是我想改用标题的原因。 @decibyte:你说得对,我误解了你的问题。我已经更新了答案。【参考方案2】:

假设您已经安装了corsheaders。编写一个 Django 中间件并将其包含在您的 MIDDLEWARE 设置中:

from django.utils.deprecation import MiddlewareMixin

class CsrfHeaderMiddleware(MiddlewareMixin):
    def process_response(self, request, response):
        if "CSRF_COOKIE" in request.META:
            # csrfviewmiddleware sets response cookie as request.META['CSRF_COOKIE']
            response["X-CSRFTOKEN"] = request.META['CSRF_COOKIE']
        return response

在您的设置中公开标题:

 CORS_EXPOSE_HEADERS = ["X-CSRFTOKEN"]

当您从您的 JS 进行 GET API 调用时,您应该从响应标头中获取 X-CSRFTOKEN,然后在您进行 POST PUT PATCH DELETE 时将其包含在请求标头中请求。

【讨论】:

谢谢,这正是我自己建议的。但它安全吗?或者我的问题有更好的解决方案吗? 设置CORS_ORIGIN_WHITELIST 只允许你的前端域,它应该足够安全。

以上是关于CSRF 和 CORS 与 Django(REST 框架)的主要内容,如果未能解决你的问题,请参考以下文章

带有 CSRF/CORS 的带有 TokenAuthentication 的 Django REST 框架

CORS、Ajax 和 CSRF

无法让 CORS 与内容类型一起使用 - Django Rest Framework

Spring + Angular 2 + Oauth2 + CORS 的 CSRF 问题

Django-Rest-Framework sessionid 和 csrftoken 未在 Chrome 上设置

rest framework认证组件和django自带csrf组件区别详解