我在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上不起作用?
原来是nginx配置问题。我的服务器设置为:
nginx-> nginx-> gunicorn
在第二个nginx系统上,我有
proxy_set_header Host $host:$server_port;
但是,由于HTTPS在第一个nginx处终止,因此$server_port
始终为80。
在HTTPS上为Django does strict referer checking(请参见第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/"
。
[就像提到的here,我通过在settings.py
中添加以下Django常量解决了这个问题,因此Django考虑了代理标头:
# Setup support for proxy headers
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
如果您查看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)