Nginx X-Forwarded-host 引起的 Django CSRF 错误

Col*_*phy 5 django ssl nginx csrf

我最近一直在开发 django 应用程序,它终于准备好部署到质量保证和生产环境中了。一切都在本地完美运行,但由于增加了现实世界部署的复杂性,我遇到了一些问题。

首先,我的技术堆栈有点复杂。对于部署,我使用 aws 进行所有操作,将我的站点部署在由负载均衡器支持的多个 ec2 上。负载均衡器使用 ssl 进行保护,但到负载均衡器的连接将通过端口 80 上的标准 http 转发到 ec2。在端口 80 上访问 ec2 后,它们将转发到端口 8000 上的 docker 容器(如果您不熟悉docker 只是认为它是一个标准的虚拟机)。在容器内部,nginx 监听端口 8000,它处理 django 中静态文件的重定向,对于 Web 请求,它会将请求转发到在 127.0.0.1:8001 上运行的 django。Django 由 uwsgi 托管,监听端口 8001。

server {
    listen   8000;
    server_name localhost;

    location /static/ {
        alias /home/library/deploy/thelibrary/static/;
    }

    location / {
        proxy_set_header X-Forwarded-Host $host:443;
        proxy_pass http://127.0.0.1:8001/;
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用 X-Forwarded 主机,因为我遇到了来自 google oauth 的重定向问题,并且重定向提示用户登录,使浏览器请求 url 127.0.0.1:8001,这显然不起作用。在我的 settings.py 文件中我还包括

USE_X_FORWARDED_HOST = True
Run Code Online (Sandbox Code Playgroud)

强制 django 使用正确的主机进行重定向。

现在,该网站的常规浏览工作正常,静态文件加载、重定向工作正常,并且该网站通过 ssl 进行保护。但问题是 CSRF 验证失败。

在提交表单时,我收到以下错误

引用检查失败 - https://qa-load-balancer.com/projects/new与https://qa-load-balancer.com:443/不匹配。

我真的不知道该怎么办,实际上是通过 stackoverflow 问题,到目前为止我一切都正常。

dus*_*ris 5

我不会使用 HTTP 代理,而是使用 Nginx 的内置功能与 uWSGI 进行通信。(如果您为 Nginx 和 uWSGI 使用单独的 Docker 容器,这仍然有效,因为通信是通过 TCP 完成的)

典型的配置(我的)如下所示:

location / {
    uwsgi_pass  http://127.0.0.1:8001;
    include     uwsgi_params;
}
Run Code Online (Sandbox Code Playgroud)

您必须--http从 uWSGI 调用中删除参数(或等效的配置文件)。

此外,在 uwsgi_params(在/etc/nginx您指定的自定义位置中找到)中,有几个用于传递元数据的指令。这是我的摘录,看起来可能与您的问题有关:

...
uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  HTTPS              $https if_not_empty;
Run Code Online (Sandbox Code Playgroud)

相关文档:http://uwsgi-docs.readthedocs.org/en/latest/WSGIquickstart.html#putting-behind-a-full-webserver


cla*_*ond 5

对于无法使用 Nginx 内置功能的用户,根本原因如下:

  • 从 ~Djagno 1.9 开始,CSRF 检查要求RefererHost匹配,除非您指定 a (请参阅此处CSRF_TRUSTED_ORIGINS的代码)REASON_BAD_REFERER
  • 如果您不指定CSRF_TRUSTED_ORIGINS,系统将回退到request.get_host()
  • request.get_host()用途request._get_raw_host()
  • request._get_raw_host()按顺序检查HTTP_X_FORWARDED_HOST(如果USE_X_FORWARDED_HOST已设置)、HTTP_HOSTSERVER_NAME
  • 最推荐的 Nginx 配置建议像这样的条目proxy_set_header X-Forwarded-Host $host:$server_port;
  • 最终,将引用者 (eg ) 与(eg )<host>进行比较。这些不匹配,因此 CSRF 失败。X-Forwarded-Host<host>:<port>

关于这一点没有太多讨论,但 Django 票证#26037引用了RFC2616。该票证指出,没有端口的主机“违反规范”,但事实并非如此,因为规范实际上是这样说的:

没有任何尾随端口信息的“主机”意味着所请求服务的默认端口

这(至少)导致以下选项(最安全的第一):

  • 包括主机和端口CSRF_TRUSTED_ORIGINS
  • port从nginx 配置中删除X-Forwarded-Host(假设非规范X-Forwarded-Host遵循与 相同的语义Host

为了避免对 中的域进行硬编码CSRF_TRUSTED_ORIGINS,第二个选项很有吸引力,但它可能会带来安全警告。推测:

  • X-Forwarded-Proto应用于阐明协议(因为缺少端口意味着默认协议)
  • 反向代理必须使用 HTTPS 端口 443(即协议的默认端口)并禁止非 HTTPS 连接类型(X-Forwarded-Proto可能会修复此问题)。