Python requests 请求 Django 站点 CSRF 校验失败解决指南

发布时间:2026/10/16 9:48:07

Python requests 请求 Django 站点 CSRF 校验失败解决指南 1. 从一个真实场景说起为什么你的请求总是被拒用 Python 的 requests 库去请求一个 Django 搭建的网站代码写得好好的URL 没错、参数也对可返回的状态码偏偏是 403。打开返回内容一看提示 CSRF verification failed。这个场景我见过太多次了不管是做接口自动化测试、写爬虫采集公开数据还是对接内部系统的 API只要目标站点用了 Django 默认的 CSRF 防护机制裸奔的 POST 请求基本都会被拦下来。这个问题的核心不在于 requests 不好用而在于 Django 的 CSRF 防护是一套完整的“令牌校验”机制它要求客户端在提交表单时必须携带一个由服务端签发、且与当前会话绑定的令牌。requests 默认不会自动处理这套机制所以你得手动把整个流程串起来。这篇文章要解决的就是这个问题如何用 requests 正确地请求带有 CSRF token 校验的 Django 站点。我会从 CSRF 的防护原理讲起然后一步步拆解完整的请求流程包括怎么拿 token、怎么带 token、怎么维持会话、怎么处理 token 过期最后把我踩过的坑和排查技巧都整理出来。适合有一定 Python 基础、正在做接口测试或数据采集的开发者参考新手也能跟着一步步操作。2. 先搞懂 Django 的 CSRF 防护到底在防什么2.1 CSRF 攻击的基本原理CSRF 全称是 Cross-Site Request Forgery跨站请求伪造。攻击的逻辑是这样的你登录了某个网站 A浏览器里存着 A 的会话 Cookie。这时候你手贱点开了另一个恶意网站 BB 的页面里藏了一个自动提交的表单目标地址指向 A 的某个敏感操作接口比如转账、改密码。因为你浏览器里带着 A 的 Cookie这个请求发出去之后A 的服务端会认为是你本人操作的于是攻击就成功了。这个攻击的关键在于浏览器会自动携带目标站点的 Cookie而服务端光凭 Cookie 无法区分这个请求是你主动发的还是被恶意页面诱导发的。2.2 Django 的令牌校验机制Django 的应对方案是引入一个额外的令牌。服务端在渲染表单页面时会生成一个随机的 CSRF token同时把它写到页面的隐藏字段里另外还会在 Cookie 里种一个名为 csrftoken 的值。当你提交表单时Django 会同时检查两样东西请求体里的 token 和 Cookie 里的 token两者必须匹配而且还要通过一套密钥验证。这就意味着攻击者的恶意页面虽然能让浏览器自动带上 Cookie但它拿不到页面里的那个隐藏 token 值所以请求就会被拒绝。这套机制的精妙之处在于token 是嵌入在页面内容里的而恶意站点受同源策略限制读不到目标站点的页面内容。2.3 为什么 requests 默认过不了这一关requests 是一个通用的 HTTP 客户端它不会去解析 HTML 页面、提取隐藏字段、维护 Cookie 和 token 的对应关系。你发一个 POST 请求它就老老实实把你能给的数据发出去没有 token 就是没有 tokenDjango 自然不认。所以我们要做的事情本质上就是模拟浏览器的行为先 GET 一次页面拿到 token把 Cookie 存下来然后在 POST 的时候把 token 带上并且保证 Cookie 和 token 是配套的。3. 完整请求流程的设计思路3.1 核心思路先取令牌再带令牌提交整个流程可以拆成三步。第一步用 GET 请求访问包含表单的页面从响应中提取 CSRF token同时让 requests 自动保存服务端下发的 Cookie。第二步从 Cookie 中读取 csrftoken 的值或者直接复用页面里提取的 token。第三步构造 POST 请求在请求头或表单数据中带上 token同时携带之前保存的 Cookie。这里有个关键点token 和 Cookie 必须是同一次会话产生的。如果你用 A 会话拿的 token 配 B 会话的 Cookie校验照样失败。所以维持会话的一致性非常重要。3.2 为什么推荐用 Session 对象而不是裸 requests很多人习惯直接用 requests.get() 和 requests.post()这样每次请求都是独立的Cookie 不会自动传递。正确的做法是创建一个 requests.Session() 对象用它来发所有请求。Session 会自动管理 Cookie第一次 GET 拿到的 csrftoken 会被存下来后续 POST 时自动带上省去了手动处理 Cookie 的麻烦。我实测下来用 Session 能让代码简洁一大半而且不容易出错。裸 requests 需要你手动从 response.cookies 里取值再塞到下次请求的 cookies 参数里稍不注意就漏了。3.3 token 放在哪里请求头还是表单数据Django 对 token 的接收位置是有讲究的。传统的表单提交token 放在 POST 数据的 csrfmiddlewaretoken 字段里。而 AJAX 请求Django 默认期望 token 放在请求头 X-CSRFToken 里。这两种方式 Django 都支持具体用哪种取决于目标接口的设计。我的经验是如果目标是一个传统的表单页面用表单字段的方式更稳妥如果是前后端分离的接口优先用请求头。拿不准的时候两种都带上也不会有问题Django 会按优先级去取。4. 手把手实现从取 token 到成功提交4.1 第一步创建 Session 并获取初始页面先看代码这是整个流程的起点import requests from bs4 import BeautifulSoup session requests.Session() # 设置一个常见的浏览器 UA避免被简单的 UA 过滤拦截 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } session.headers.update(headers) # 访问包含表单的页面 login_url https://example-site.com/login/ response session.get(login_url) print(response.status_code)这一步做完之后session 对象里已经存了服务端下发的 csrftoken Cookie。你可以打印 session.cookies 看一下通常能看到一个名为 csrftoken 的条目。注意有些站点会把 token 藏在 JavaScript 变量里而不是隐藏字段里这种情况用 BeautifulSoup 解析 HTML 是拿不到的需要额外用正则去匹配 script 标签里的内容。4.2 第二步从页面中提取 CSRF token拿到页面 HTML 之后用 BeautifulSoup 定位隐藏字段soup BeautifulSoup(response.text, html.parser) # Django 表单里的隐藏字段名固定是 csrfmiddlewaretoken token_input soup.find(input, {name: csrfmiddlewaretoken}) if token_input: csrf_token token_input.get(value) print(提取到的 token:, csrf_token) else: print(页面里没有找到 csrfmiddlewaretoken 字段)如果页面里确实没有这个字段别急着放弃。先去 session.cookies 里看看有没有 csrftoken有的话可以直接用那个值。Django 的校验逻辑是请求体里的 token 和 Cookie 里的 token 经过同样的密钥处理后应该一致而页面里的 csrfmiddlewaretoken 本身就是从 Cookie 的 csrftoken 派生出来的所以直接用 Cookie 里的值往往也能通过。4.3 第三步构造带 token 的 POST 请求现在把 token 带上提交表单post_url https://example-site.com/login/ # 方式一token 放在表单数据里 form_data { username: your_username, password: your_password, csrfmiddlewaretoken: csrf_token, } # 方式二token 放在请求头里AJAX 风格 post_headers { X-CSRFToken: csrf_token, Referer: login_url, } response session.post(post_url, dataform_data, headerspost_headers) print(response.status_code) print(response.text[:500])这里我同时用了两种方式实际项目中选一种即可。另外注意 Referer 头Django 在 HTTPS 环境下会校验 Referer 是否同源如果缺失或者不对也会触发 403。这个坑我踩过当时排查了半天才发现是 Referer 的问题。4.4 完整代码整合把上面的步骤串起来形成一个可以直接运行的脚本import requests from bs4 import BeautifulSoup def login_with_csrf(login_url, post_url, username, password): session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 }) # 第一步GET 页面拿 Cookie 和 token resp session.get(login_url) soup BeautifulSoup(resp.text, html.parser) token_input soup.find(input, {name: csrfmiddlewaretoken}) if token_input: csrf_token token_input.get(value) else: csrf_token session.cookies.get(csrftoken) if not csrf_token: raise RuntimeError(未能获取到 CSRF token检查页面结构或 Cookie) # 第二步POST 提交 form_data { username: username, password: password, csrfmiddlewaretoken: csrf_token, } headers { X-CSRFToken: csrf_token, Referer: login_url, } resp session.post(post_url, dataform_data, headersheaders) return resp if __name__ __main__: result login_with_csrf( https://example-site.com/login/, https://example-site.com/login/, your_username, your_password ) print(result.status_code)这段代码覆盖了大部分 Django 站点的登录场景。你可以根据目标站点的实际字段名做调整。5. 参数细节与常见坑点解析5.1 Cookie 与 token 的绑定关系Django 的 CSRF 校验有一个容易被忽略的细节它会把 Cookie 里的 csrftoken 和请求里的 token 做一次“加盐”处理然后比对结果。这个加盐用的密钥是服务端随机生成的每次请求可能不同。所以你不能自己伪造一个 token必须用服务端下发的。另外如果你在同一个 Session 里多次 GET 页面csrftoken 可能会被刷新。这时候旧的 token 就失效了必须用最新的。我遇到过一种情况先 GET 了页面 A 拿到 token然后又 GET 了页面 B结果 B 刷新了 Cookie导致 A 的 token 作废。解决办法是每次 POST 之前重新 GET 一次目标页面确保 token 是最新的。5.2 Referer 校验的触发条件Django 的 CsrfViewMiddleware 在 HTTPS 请求下会检查 Referer 头。如果请求是 HTTPS 的而 Referer 缺失或者域名不匹配就会直接拒绝。这个行为由 CSRF_TRUSTED_ORIGINS 配置控制但作为客户端我们改不了服务端配置只能老老实实把 Referer 带上。实测下来Referer 的值应该是目标站点的完整 URL包括协议和域名。有些站点还会校验路径所以最好用表单页面的 URL 作为 Referer。5.3 token 过期与刷新策略CSRF token 是有有效期的默认和会话绑定。如果会话过期了token 也就失效了。在长时间运行的脚本里你需要处理 token 过期的情况。我的做法是封装一个重试逻辑如果 POST 返回 403就重新 GET 一次页面刷新 token然后再试一次。def post_with_retry(session, url, data, headers, max_retries2): for attempt in range(max_retries): resp session.post(url, datadata, headersheaders) if resp.status_code ! 403: return resp # 403 了刷新 token 再试 page session.get(url) soup BeautifulSoup(page.text, html.parser) token_input soup.find(input, {name: csrfmiddlewaretoken}) if token_input: new_token token_input.get(value) data[csrfmiddlewaretoken] new_token headers[X-CSRFToken] new_token return resp这个重试逻辑在实际项目中非常实用尤其是做批量操作的时候。5.4 常见问题速查表问题现象可能原因解决方向返回 403提示 CSRF verification failed请求里没有 token检查是否提取并携带了 token返回 403提示 CSRF cookie not setCookie 里没有 csrftoken确认先 GET 了页面且 Session 正常保存 Cookie返回 403提示 Referer checking failedReferer 缺失或不对添加正确的 Referer 头token 提取为空页面结构变了或 token 在 JS 里检查页面源码改用正则匹配第一次成功第二次失败token 被刷新了每次请求前重新获取 token本地测试通过线上失败环境差异导致 Cookie 策略不同检查域名、协议、端口是否一致6. 进阶场景接口测试与批量请求中的实践6.1 在接口自动化测试中复用 Session做接口测试的时候通常需要先登录拿到会话然后连续调用多个接口。这时候 Session 对象的复用就很重要。我的做法是把登录逻辑封装成一个 fixture在测试开始前执行一次后续所有请求都复用这个 Session。import pytest pytest.fixture(scopesession) def logged_in_session(): session requests.Session() # 执行登录流程 # ... return session def test_get_profile(logged_in_session): resp logged_in_session.get(https://example-site.com/api/profile/) assert resp.status_code 200这样写的好处是登录只做一次测试跑得快而且不会因为频繁登录触发风控。6.2 处理前后端分离的接口现在很多 Django 项目用 DRF 做后端前端用 Vue 或 React。这种架构下CSRF token 通常通过 Cookie 下发前端从 Cookie 里读出来放到请求头里。用 requests 模拟的时候流程是一样的先 GET 一个接口让服务端种下 csrftoken然后从 session.cookies 里读出来放到 X-CSRFToken 头里。需要注意的是有些 DRF 项目会关闭 CSRF 校验用 csrf_exempt 装饰器这时候你反而不用带 token。所以动手之前先用浏览器开发者工具看一下实际请求带了什么照着抄就行。6.3 批量请求时的性能考量如果需要发起大量请求每次都 GET 页面拿 token 会很慢。优化思路是登录之后token 在会话有效期内是复用的不需要每次请求都重新获取。只有在收到 403 的时候才去刷新。这样能把请求量降下来。另外Session 对象本身是线程不安全的如果你用多线程并发请求每个线程应该有自己的 Session。这一点在做压力测试的时候要特别注意。7. 我踩过的那些坑和排查心得第一个坑是 UA 的问题。有些站点会对 requests 默认的 User-Agent 做拦截直接返回 403但提示信息不是 CSRF 相关的容易误导排查方向。所以第一步就应该把 UA 设置成常见浏览器。第二个坑是 Cookie 的作用域。如果你访问的页面和提交的接口不在同一个路径下Cookie 的 path 属性可能导致 Cookie 不被发送。这种情况需要检查 Set-Cookie 头里的 path 字段必要时手动调整。第三个坑是 HTTPS 证书。有些测试环境用的是自签名证书requests 默认会校验失败。可以用 verifyFalse 跳过但生产环境千万别这么干。排查的时候我习惯用 curl 或者浏览器开发者工具先手动跑一遍流程把实际的请求头、Cookie、表单数据都抓下来然后照着用 requests 复现。这样比盲猜快得多。另外一个技巧是打开 Django 的日志看看服务端到底是因为哪个环节拒绝的日志里通常会写明具体原因。最后分享一个小经验如果你拿到的 token 里包含换行符或者空格记得 strip 一下。我有一次就是因为 token 末尾多了个换行导致校验一直失败排查了快一个小时才发现。这种细节问题最折磨人但也最容易避免。
延伸阅读

更多相关文章

2026/10/16 9:48:07

SQL注入之报错注入全解析:原理、姿势与防御技巧

有的新手朋友第一次接触 SQL注入时都会忍不住想:既然有 union 注入可以直接回显数据,那报错注入到底还有什么用?说实话,最初我也有同样的疑问。后来在实际测试里碰了几次壁,才真正意识到报错注入的特殊价值——它不需要…

2026/10/16 9:42:45

阿里云K8s部署Vue2+SpringBoot2.5+Nacos2.0.3全流程

简介:这份资源面向需要在阿里云Kubernetes集群上落地前后端分离项目的开发与运维人员,提供一套可直接参考的部署方案,覆盖Vue2前端、Nginx、SpringBoot2.5后端与Nacos2.0.3注册配置中心的完整编排。包内共16个文件,以8个yaml清单为…

2026/10/16 9:42:45

K8s 部署 Vue2 + Spring Boot 2.5 + Nacos 2.0.3 实战清单与避坑指南

简介:本资源面向需要在阿里云Kubernetes集群上落地前后端分离项目的开发与运维人员,提供一套可直接参考的部署方案,覆盖Vue2前端、Nginx反向代理、SpringBoot2.5后端以及Nacos2.0.3注册配置中心的完整编排。包内共16个文件,以8个Y…

2026/10/16 10:54:16

ConvLSTM实战:伦敦空气质量时空预测全流程解析

1. 问题定义与模型选型:为什么偏偏是ConvLSTM上一篇我们完成了伦敦地区空气质量数据的获取和预处理。拿到了20多个监测站点的逐小时污染物浓度记录,做了缺失值插补,也看了基本的时间分布规律。但数据准备好了并不代表能直接进入训练&#xff…

2026/10/16 10:54:15

ASP.NET MVC TempData 机制:重定向传值到自定义 Provider 全解析

如果你做过一段时间的 MVC 后台项目,大概率遇到过一个特别不起眼又特别烦人的需求:用户点了一下“保存”,服务器处理完要重定向回列表页,还得在列表页上带一句话——“保存成功”或者“数据已更新”。刚开始我直接用 ViewBag 传&a…

2026/10/16 10:54:15

AI Agent 集成 GitHub Actions:CI 流水线自动化实战

1. 为什么要把 AI Agent 塞进 CI 流水线 第一次跟同行聊起"让 AI Agent 跑在 GitHub Actions 上"这个想法时,对方的第一反应是:"你图啥?本地跑个脚本不香吗?"这个问题我后来被问过不下十次,每次我…

2026/10/16 10:54:15

8路强电电源管理器详解:从原理到选型与实战排障

我在项目现场见过太多因为电源管理不当翻车的情况——功放关机瞬间“噗”的一声把音箱烧了,投影机散热风扇没转完就被强行断电导致灯泡提前报废,会议室里十几台设备靠人工去挨个插拔排插,稍不留神就漏掉一路。后来把8路强电电源管理器搭进中控…

2026/10/16 10:43:45

二叉树层序遍历详解:从BFS队列到LeetCode变体应用

说实话,每次看到二叉树的层序遍历,我都会想起一个场景:排队做核酸。你站在队伍里,只能挨个往前挪,先来的先测,后来的排后面,这是不是就像二叉树的每一层节点,从左到右依次入队、出队…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/16 0:01:11

Cephadm实战指南:Linux系统工程师的生产级Ceph集群部署与排障

简介:本资源是一份完整的Ceph分布式存储系统中文技术文档合集,面向Linux系统运维工程师、云平台架构师及存储方向开发者,帮助读者系统掌握Ceph集群的部署、配置与运维核心能力。文档覆盖对象存储(RGW)、块设备&#xf…

2026/10/16 0:01:11

ITK中SpatialObject几何体系解析:坐标系、变换与管道集成

1. 空间对象的定位:为什么 ITK 需要一套几何描述体系很多刚开始接触 ITK 的朋友,包括我自己,最初都是被它的图像滤波、配准、分割这些“正统”功能吸引进来的。等真正上手做点实际项目,比如把分割出来的器官边界做量化分析、给血管…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑