Django 4搭配Caddy反向代理部署时CSRF保护拦截问题排查
根本原因
这个问题和Daphne、Caddy的版本没有直接关系,核心是三个容易被忽略的规则变化和默认逻辑:
- Django 4.0+的CSRF校验逻辑变更:从4.0版本开始,CSRF中间件的Origin校验不再自动复用
ALLOWED_HOSTS的配置,两个配置的职责完全拆分:ALLOWED_HOSTS负责校验请求的Host头,防御主机头攻击CSRF_TRUSTED_ORIGINS负责校验跨域请求的Origin源,所有POST/PUT/DELETE等非安全方法的请求,只要带了Origin头,就必须严格匹配这个列表里的值才会放行
旧版本Django会自动把ALLOWED_HOSTS里的源加入CSRF信任列表,所以老部署教程不会提这个配置,你看到的过时教程都是基于旧版本逻辑写的。
- 回环地址是CSRF校验的硬编码豁免源:Django默认直接信任
localhost、127.0.0.1以及IPV6回环地址的请求,不管你有没有在配置里加这些源,都不会触发Origin校验失败。这就是你走SSH隧道直连容器(访问localhost:8088)完全正常的原因,不是配置正确,是回环地址本身被豁免了。 - 你遇到的SSL重定向循环,是因为Django默认不信任反向代理传递的协议头,不知道Caddy已经帮你做了HTTPS卸载,收到代理转发的HTTP请求后会尝试重定向到HTTPS,Caddy收到重定向响应又转回给Django,就形成了死循环。
修复步骤
按顺序操作即可,不需要改复杂的Caddy头配置:
- 修改Django settings.py配置
先添加源和代理相关配置:
配置完这三个参数后,你之前临时关闭的三个安全配置就可以正常打开,不会再出现重定向循环:# 替换成你自己的域名,必须带完整协议头 CSRF_TRUSTED_ORIGINS = ["https://<mydomain.com>"] # 信任反向代理传递的Host头 USE_X_FORWARDED_HOST = True # 识别反向代理传递的HTTPS协议标识 SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True - 调整Daphne启动参数
Daphne默认会忽略反向代理传递的X-Forwarded-*头,启动时必须加--proxy-headers参数才会把代理头透传给Django,启动命令参考:daphne --proxy-headers --bind 0.0.0.0 --port 8088 你的项目名.asgi:application - Caddy配置无需额外修改
你现在用的极简反向代理配置是正确的,Caddy 2.x默认会自动向后端传递X-Forwarded-For、X-Forwarded-Proto、X-Forwarded-Host这几个必要的头,不需要手动加header配置,改完上面两个配置重启服务即可正常使用。
反向代理场景CSRF问题通用调试方法
遇到CSRF拦截不要盲目改配置,按以下步骤定位基本都能快速解决:
- 先看CSRF报错页给出的具体失败原因,Django的CSRF中间件会明确标注失败类型(比如这次是Origin不匹配,还有常见的Cookie不匹配、Referer不匹配等),直接对应原因找配置问题即可,不用无差别试错。
- 打开浏览器开发者工具的网络面板,找到触发CSRF报错的POST请求,核对三个值:请求的
Origin头具体是什么、请求是否携带了正确的csrftokenCookie、Cookie的域和Secure属性和当前站点是否匹配。 - 临时加一个调试视图打印请求元信息,确认Django实际接收到的参数和浏览器发送的一致:
如果打印出来的HTTPS状态不对、Host不对,就是反向代理头透传或者服务启动参数有问题。from django.http import HttpResponse def debug(request): return HttpResponse(f""" ORIGIN: {request.META.get('HTTP_ORIGIN')}<br> HOST: {request.get_host()}<br> IS_HTTPS: {request.is_secure()}<br> CSRF_COOKIE: {request.COOKIES.get('csrftoken')} """) - 核对
CSRF_TRUSTED_ORIGINS的格式:必须带完整协议头(http/https),如果是非标准端口还要带端口号,结尾不要加斜杠,必须和请求的Origin头完全一致才能匹配。
内容的提问来源于stack exchange,提问作者aspyct
相关产品推荐
相关产品推荐

