Django经Nginx反向代理时CSRF验证失败问题咨询
Django CSRF 问题排查记录
环境配置
localhost (Windows 11) - Nginx 监听80和443端口,80端口未自动重定向至443 均反向代理至http://wsgi-server(wsgi-server=127.0.0.1:8080) - waitress_wsgi 作为服务运行在8080端口
Django配置
模板文件(email_test.html)
<!-- email_test.html --> <!-- ... --> <form action="{% url 'identity:email_test' %}" method="post"> {% csrf_token %} {{ email_form }} {% translate 'Send email' as submit_translated %} <!-- 我使用django_bootstrap5 --> {% bootstrap_button button_type="submit" content=submit_translated extra_classes='w-100'%} </form>
配置文件(settings.py)
# settings.py ---------- MIDDLEWARE = { # ... 'django.middleware.csrf.CsrfViewMiddleware', # ... }
表单定义(forms.py)
# forms.py ------------- class EmailTestForm(forms.Form): email = forms.EmailField( # help_text=password_validation.password_validators_help_text_html(), label=_('Email'), max_length=128, )
视图逻辑(views.py)
# views.py ------------- def email_test(request): context = {} context.update(template_globals()) if request.method == "POST": email_form = EmailTestForm(request.POST) if email_form.is_valid(): email_obj = EmailMessage(subject='Hello', body='Email body', from_email='noreply@nutrihub.hnet', to=[email_form.cleaned_data.get('email')]) email_obj.send(fail_silently=False) else: email_form = EmailTestForm() context['email_form'] = email_form return render(request, "identity/email_test.html", context)
浏览器访问测试结果
py manage.py runserver(默认端口8000),浏览器访问http://127.0.0.1:8000,settings.CSRF_TRUSTED_ORIGINS为空:运行正常。- 浏览器访问
http://localhost/http://127.0.0.1/https://localhost/https://127.0.0.1,对应地址未加入settings.CSRF_TRUSTED_ORIGINS:出现CSRF错误。 - 浏览器访问
http://localhost/http://127.0.0.1/https://localhost/https://127.0.0.1,对应地址已加入settings.CSRF_TRUSTED_ORIGINS:运行正常。 - 浏览器访问
https://mymachine.net(该域名在etc/hosts中指向127.0.0.1),未加入settings.CSRF_TRUSTED_ORIGINS:出现CSRF错误。 - 浏览器访问
https://mymachine.net(该域名在etc/hosts中指向127.0.0.1),已加入settings.CSRF_TRUSTED_ORIGINS:运行正常。 - 浏览器访问
http://localhost:8080/http://localhost/http://mymachine.net:8080:运行正常。
问题解答
是的,这个现象的核心原因就是通过Nginx转发的请求,在Django眼里和浏览器的请求源不属于同站点。
具体逻辑如下:
- Django的CSRF验证核心是比对「浏览器发起请求的源」和「Django感知到的请求源」是否一致。
- 当用Nginx反向代理时,Django默认获取的是Nginx转发过来的请求地址(即WSGI服务器的
127.0.0.1:8080),但浏览器的请求源是localhost/127.0.0.1/mymachine.net(对应80/443端口),两者完全不匹配,因此被判定为跨站请求,触发CSRF错误。 - 将这些外部源加入
settings.CSRF_TRUSTED_ORIGINS后,相当于告知Django这些域名/端口的请求是可信的,允许通过CSRF验证,所以场景3、5能正常运行。 - 而场景6直接访问8080端口时,浏览器的请求源(如
localhost:8080)和Django感知到的源一致,属于同站点请求,无需额外配置即可通过验证。
内容的提问来源于stack exchange,提问作者fishfin
相关产品推荐
相关产品推荐

