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

时间:2014-12-17 19:09:09

标签: django ssl nginx csrf

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

首先我的技术堆栈有点复杂。对于部署,我使用aws来处理我的站点部署在由负载均衡器支持的多个ec2上的所有内容。负载均衡器使用ssl进行保护,但与负载均衡器的连接通过端口80上的标准http转发到ec2。在端口80上按下ec2后,它们将转发到端口8000上的docker容器(如果您不熟悉docker只是认为它是一个标准的vm)。在容器内部,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/;
    }
}

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

USE_X_FORWARDED_HOST = True

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

现在,网站的一般浏览工作完美,静态文件加载,重定向工作,网站使用ssl保护。但问题是CSRF验证失败。

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

参与者检查失败 - https://qa-load-balancer.com/projects/newhttps://qa-load-balancer.com:443/不匹配。

我真的不知道该怎么做,它真的是通过stackoverflow问题,到目前为止我已经完成了所有工作。

2 个答案:

答案 0 :(得分:4)

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

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

location / {
    uwsgi_pass  http://127.0.0.1:8001;
    include     uwsgi_params;
}

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

此外,在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;

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

答案 1 :(得分:1)

对于不能使用Nginx内置工具的用户,这是根本原因:

  • 从~Djagno 1.9开始,CSRF检查要求RefererHost匹配,除非您指定CSRF_TRUSTED_ORIGINS(请参阅REASON_BAD_REFERER周围的代码{{3} })
  • 如果您未指定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;
  • 这样的条目
  • 最终,引荐来源(例如<host>)与X-Forwarded-Host进行比较(例如<host>:<port>)。这些不匹配,因此CSRF失败。

对此没有太多讨论,但Django票证here引用了#26037。该票证明没有端口的主机是“违规”,但这不符合规范实际上说:

  

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

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

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

为了避免在CSRF_TRUSTED_ORIGINS中对域进行硬编码,第二种选择很有吸引力,但它可能带有安全性警告。推测:

    应该使用
  • X-Forwarded-Proto来阐明协议(因为没有端口意味着默认协议)
  • 反向代理必须使用端口443进行HTTPS(即协议的默认设置)并禁止非HTTPS连接类型(X-Forwarded-Proto可能会解决此问题。)