CSRF 和 CORS 与 Django(REST 框架)

CSRF and CORS with Django (REST Framework)

我们正在将我们的前端移动到一个单独的项目中(在 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 令牌作为 header 并且(通过 Access-Control-Expose-Headers)允许前端读取此 header,我们是否会损害 CSRF 保护?

鉴于我们已经为 API 正确设置了 CORS(即只允许某些域对 API 进行跨源请求),第 3 方网站上的 JS 应该无法阅读此回复 header,因此无法在用户背后提出妥协 AJAX 请求,对吧?还是我错过了一些重要的东西?

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

假设您已经安装了 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

在您的设置中公开 header:

 CORS_EXPOSE_HEADERS = ["X-CSRFTOKEN"]

当您从 JS 发出 GET API 调用时,您应该从响应 header 中获得 X-CSRFTOKEN,继续将其包含在请求中 header 当你发出 POST PUT PATCH DELETE 请求时。

一开始我不明白你的问题,所以请允许我总结一下:你无法从客户端的 cookie 中获取 CSRF 令牌,因为同源策略阻止你访问 cross-domain cookie (即使使用 CORS)。因此,您建议服务器改为以自定义方式 header 将 cookie 传输到客户端,并且想知道这是否安全。

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

撇开这个警告不谈,我认为您的建议没有任何安全问题。同源策略将阻止 third-party 站点像读取 body 一样读取 header,您可以选择使用 CORS [=] 从您的客户端域读取它们11=] header.

您可能会发现 this answer 很有趣,因为它列出了各种 CSRF 令牌方案的优缺点。它包括使用自定义响应 header,并且针对您的问题确认:"If a malicious user tries to read the user's CSRF token in any of the above methods then this will be prevented by the Same Origin Policy".

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