不装任何第三方库,用 Python 标准库做一次带 CSRF 的 HTTP 调用

举报
yd_262967087 发表于 2026/10/25 16:45:44 2026/10/25
【摘要】 很多内部接口需要 CSRF token,而 token 藏在响应头里。这篇用 urllib 演示完整流程:拿 token、带 token 请求、处理 403,以及为什么有时必须换浏览器。

场景

要给一个内部系统做自动化,接口需要 CSRF token。环境不允许装 `requests`,只能用标准库。

一、先看清楚 token 从哪来

打开浏览器开发者工具,看请求头。常见三种来源:

1. **响应头下发**:`GET /api/bootstrap` 的响应头里有 `csrf: xxx`

2. **Cookie**:`csrf-token=xxx`,前端读出来塞进请求头

3. **页面内嵌**:HTML 里 ``

我遇到的是第一种。判断方法:在页面控制台执行 `document.cookie`,如果里面没有 csrf,那大概率是响应头或内存变量。

二、用 urllib 实现


import json
import urllib.request
import urllib.error

UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/140.0 Safari/537.36"


def get(url, cookie="", extra=None):
    headers = {"User-Agent": UA, "Accept": "application/json"}
    if cookie:
        headers["Cookie"] = cookie
    if extra:
        headers.update(extra)
    req = urllib.request.Request(url, headers=headers)
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            return r.status, dict(r.headers), r.read().decode()
    except urllib.error.HTTPError as e:
        return e.code, dict(e.headers), e.read().decode("utf-8", "replace")


# 第一步:拿 token
status, headers, _ = get("https://example.com/api/bootstrap")
csrf = {k.lower(): v for k, v in headers.items()}.get("csrf")

# 第二步:带上它
status, headers, body = get(
    "https://example.com/api/data",
    cookie="sid=abc123",
    extra={"csrf": csrf},
)
print(status, body[:200])

三、Cookie 拼接有个陷阱

如果直接把所有 cookie 拼成一个字符串,很容易出错。原因:

• 有些 cookie 是 host-only(只对特定域名生效)

• 有些是 domain cookie(对子域生效)

• 同名 cookie 在父子域可能各有一份

正确做法是按目标 host 过滤:


from urllib.parse import urlparse


def jar_for(cookies, url):
    host = urlparse(url).hostname
    jar = {}
    for c in cookies:
        dom = c["domain"].lstrip(".")
        if c["domain"].startswith("."):
            if not (host == dom or host.endswith("." + dom)):
                continue
        elif host != dom:
            continue
        prev = jar.get(c["name"])
        # 更具体的域优先
        if prev is None or len(dom) > len(prev[0]):
            jar[c["name"]] = (dom, c["value"])
    return jar

我之前就是因为把所有 cookie 无脑拼上,导致请求打到了错误的边缘节点,返回 `404 no Route matched`——排查了很久才发现是 cookie 污染。

四、什么时候该放弃纯 HTTP

如果出现以下情况,说明纯 HTTP 走不通,该上浏览器:

• 补了 token、cookie、完整请求头,仍然 403

• 响应内容明确说「未登录」,但浏览器里同一个账号是登录状态

• 服务器识别到非浏览器指纹(TLS 指纹、`sec-ch-ua` 等)

我遇到的真实案例:同一个 profile,**无头浏览器**返回未登录,**非无头浏览器**正常。这种只能上真实浏览器 + 虚拟显示器。

五、错误处理的几个细节


try:
    ...
except urllib.error.HTTPError as e:
    # 注意:HTTPError 本身也是 response,能读 body
    body = e.read().decode("utf-8", "replace")
    if "login" in body.lower():
        raise AuthExpired("登录态失效")
except urllib.error.URLError as e:
    # 网络层错误(DNS、超时、拒绝连接)
    raise NetworkError(str(e.reason))

区分「HTTP 层失败」和「网络层失败」很重要,前者要重新登录,后者只需重试。

小结

标准库够用,但要处理好三件事:token 来源、cookie 作用域、错误分类。如果这些都对了还是不通,果断换浏览器,别在 HTTP 层死磕。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。