Django返回403 Forbidden并提示CSRF token missing or incorrect,是因为非安全请求未携带或携带了不匹配的CSRF令牌;需确保模板中正确使用{% csrf_token %}、AJAX请求手动设置X-CSRFToken头、Cookie未被拦截且配置一致。

为什么Django返回403 Forbidden并提示CSRF token missing or incorrect?
这通常不是配置错误,而是请求中根本没带csrf_token,或者带了但和服务器生成的不匹配。Django默认对所有非安全方法(POST/PUT/PATCH/DELETE)强制校验CSRF,且要求csrftoken Cookie与表单或请求头中的csrfmiddlewaretoken值一致。
常见诱因包括:前端没渲染{% csrf_token %}、AJAX请求没传X-CSRFToken头、Cookie被浏览器拦截(比如SameSite=Lax下跨站跳转后丢失)、或用户清除了Cookie但没刷新页面。
模板中表单提交时CSRF失效的典型修复方式
Django模板必须显式插入{% csrf_token %},且它必须在<form>标签内部、<input type="submit">之前。它会自动渲染一个隐藏字段<input type="hidden" name="csrfmiddlewaretoken" value="...">,同时设置csrftoken Cookie。
- 确认模板里有
{% csrf_token %},不是写成{{ csrf_token }}(后者只输出value,不设Cookie) - 检查是否用了
csrf_protect装饰器覆盖了默认行为,导致重复校验 - 如果使用
render_to_response(已弃用),改用render——前者不自动注入RequestContext,{% csrf_token %}会失效 - 避免在
CSRF_COOKIE_SECURE = True时用HTTP访问(此时Cookie仅HTTPS传输,HTTP请求拿不到csrftoken)
AJAX POST请求触发CSRF 403的处理步骤
jQuery或fetch发送POST时不会自动附带CSRF信息,需手动提取Cookie并设请求头。Django要求X-CSRFToken头值等于csrftoken Cookie内容。
立即学习“Python免费学习笔记(深入)”;
示例(通用JS提取方式):
function getCookie(name) {
let cookieString = document.cookie;
let cookies = cookieString.split('; ');
for (let cookie of cookies) {
if (cookie.startsWith(name + '=')) {
return cookie.substring(name.length + 1);
}
}
return null;
}
const csrftoken = getCookie('csrftoken');
fetch('/api/submit/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify({data: 'xxx'})
});
- 确保
CSRF_COOKIE_HTTPONLY = False(默认为False),否则JS无法读取csrftokenCookie - 若用Axios,可在全局interceptor里统一加
X-CSRFToken头 - 不要把
csrfmiddlewaretoken从表单里手动取值当CSRF Token用——它和Cookie里的csrftoken是同一串,但仅限于该次渲染;Cookie才是服务端比对依据
CSRF相关设置引发的隐蔽问题
修改settings.py中CSRF配置时,容易忽略环境差异和中间件依赖。
-
CSRF_COOKIE_SAMESITE = 'Lax'(默认)在跨站重定向后可能丢Cookie,测试时用'None'需同步设CSRF_COOKIE_SECURE = True,否则报错 - 禁用CSRF(如
@csrf_exempt)仅适用于明确无需防护的接口,不能用于含用户凭证的POST;误用会导致安全漏洞 - 如果用了Nginx反向代理,确认没删掉
Cookie头或重写Host导致Referer校验失败(Django默认检查Referer是否匹配ALLOWED_HOSTS) -
CSRF_HEADER_NAME被改成HTTP_X_CUSTOM_CSRF之类时,前端必须同步改请求头名,否则服务端根本读不到
最常被忽略的是:CSRF校验失败时Django不输出具体原因,默认只返回403。开启DEBUG = True能看到更详细的拒绝理由,比如“Referer checking failed”或“CSRF cookie not set”,这些线索比单纯看403有用得多。


















