8

我有一个位于http://example.com的 Django 网站,它运行良好,包括发布请求。我添加了 HTTPS,因此我的网站也可以在https://example.com上访问。

我可以在 HTTPS 上加载任何页面,但是当我尝试 POST 时,我总是遇到 CSRF 验证错误。POST 请求在 HTTP 上运行良好。

我的 Django 进程在 nginx 后面使用 gunicorn 运行,并且我有 nginx 设置X_Forwarded_For。因此,HTTPS 请求具有以下标头(取自request.META):

'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.5',
'HTTP_CONNECTION': 'close',
'HTTP_COOKIE': 'redacted',
'HTTP_HOST': 'example.com:80',
'HTTP_REFERER': 'https://example.com/user/delete/49/',
'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0',
'HTTP_X_FORWARDED_FOR': '1.2.3.4, 192.168.252.22',
'HTTP_X_FORWARDED_PROTO': 'https',
'HTTP_X_REAL_IP': '1.2.3.4',

并且 HTTP 请求具有以下标头:

'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.5',
'HTTP_CONNECTION': 'close',
'HTTP_COOKIE': 'redacted',
'HTTP_HOST': 'example.com.com:80',
'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0',
'HTTP_X_FORWARDED_FOR': '1.2.3.4, 192.168.252.22',
'HTTP_X_FORWARDED_PROTO': 'http',
'HTTP_X_REAL_IP': '1.2.3.4',

当我在 HTTP 上没有问题时,为什么 CSRF 不能在 HTTPS 上工作?

4

5 回答 5

11

原来这是一个nginx配置问题。我的服务器设置是:

nginx -> nginx -> gunicorn

在第二个 nginx 系统上,我有

proxy_set_header        Host            $host:$server_port;

但是,由于 HTTPS 在第一个 nginx 处终止,$server_port因此始终为 80。

在 HTTPS 上,Django 会进行严格的referer 检查(参见第 4 点)。查看 Django 源代码:

good_referer = 'https://%s/' % request.get_host()
if not same_origin(referer, good_referer):
    reason = REASON_BAD_REFERER % (referer, good_referer)
    logger.warning('Forbidden (%s): %s', reason, request.path,
        extra={
            'status_code': 403,
            'request': request,
        }
    )
    return self._reject(request, reason)

CSRF 验证失败,因为"https://example.com/" != "https://example.com:80/".

于 2013-10-25T15:26:33.533 回答
1

就像这里提到的,我通过添加以下 Django 常量解决了这个问题,settings.py所以 Django 考虑代理标头:

# Setup support for proxy headers
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
于 2019-10-30T10:48:48.753 回答
0

就我而言,我有一个使用 HTTPS 的公共主机,但内部主机是 HTTP,因此转发https://myhost.com/path/失败http://internal-host.myhost.com/path。这是配置的方式:

location @proxy_to_app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    # the following means the $scheme e.g. https is forwarded
    # disabling this fixes the problem
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
}
于 2022-02-23T16:15:10.583 回答
0

您的CSRF_TRUSTED_ORIGINS设置错误 - 将其更改为:

CSRF_TRUSTED_ORIGINS = ['similarchords.com', 'www.similarchords.com'] 
于 2021-09-13T19:45:47.590 回答
-2

如果你看 CsrfViewMiddleware的实现

Django 在request.is_secure()时检查“Referer”标头

good_referer = 'https://%s/' % request.get_host()
if not same_origin(referer, good_referer):
    reason = REASON_BAD_REFERER % (referer, good_referer)
    return self._reject(request, reason)
于 2014-11-13T13:02:10.667 回答