签名 URL 刷新导致审批失效?别急着删掉所有 query

签名 URL 刷新导致审批失效?别急着删掉所有 query

一个真实而隐蔽的失败是:平台草稿连续两次回读,标题、正文和图片对象都相同,内容 SHA 却不一样。原因不是有人改稿,而是图片 CDN 每次返回了新的过期时间和签名参数。

如果批准令牌绑定原始 Markdown SHA,签名刷新会让刚签发的批准立即失效;如果为了省事删除所有 URL 查询参数,又可能把本应不同的资源合并。这个问题的核心不是"哈希算法怎么选",而是安全边界应该忽略什么、继续绑定什么。

冲突:密码学正确,业务语义却错位

SHA-256 对一个字符的变化都敏感,这是优点。可审批系统关心的可能是"图片对象有没有换",而不是"访问这个对象的短期凭证有没有刷新"。

因此需要同时承认两件事:

  • 原始 Markdown 的确变了,不能伪装成字节完全一致。
  • 如果只变了受信任图片 URL 的短期签名,发布对象可能仍是同一个。

把两个判断压成一个 SHA,迟早会让存储完整性和业务身份互相打架。

推荐决策:双哈希,而不是放宽一个哈希

我的推荐是保留两条证据链:

python 复制代码
strict_sha = sha256(raw_markdown)
approval_sha = sha256(canonicalize_signed_images(raw_markdown))

strict_sha 服务于存储、审计和逐字回读;approval_sha 只服务于已经定义过等价关系的批准或幂等边界。批准记录仍绑定文章 ID、revision 和草稿 ID,稳定摘要不能单独成为通行证。

规范化函数也必须是窄能力:只处理 Markdown 图片 URL,只接受 HTTPS,只命中明确的 CDN 域名,并且只删除已经验证会刷新的签名字段。

为什么不能直接 url.split('?')[0]

因为 query 未必都是凭证。尺寸、格式、语言、版本甚至对象选择都可能由 query 决定。下面三种变化必须区分:

  1. signatureexpires 刷新:在白名单契约下可以视为同一对象。
  2. /bucket/a.png 变成 /bucket/b.png:对象变了,必须拒绝旧批准。
  3. ?version=1 变成 ?version=2:如果版本影响内容,也必须拒绝。

安全规范化不是"去掉噪声",而是把经过证据确认的易变凭证与资源身份分离。

最小实现与关键测试

python 复制代码
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit

EPHEMERAL = {"signature", "expires", "policy"}
TRUSTED = {"img.example.com"}

def canonical_url(url: str) -> str:
    parsed = urlsplit(url)
    if parsed.scheme != "https" or parsed.hostname not in TRUSTED:
        return url
    stable_query = [
        (key, value)
        for key, value in parse_qsl(parsed.query, keep_blank_values=True)
        if key not in EPHEMERAL
    ]
    return urlunsplit(
        (parsed.scheme, parsed.netloc, parsed.path, urlencode(stable_query), "")
    )

测试不能只覆盖"签名刷新后相等",还必须证明边界没有被放宽:

  • 域名改变,稳定摘要必须改变。
  • 对象路径改变,稳定摘要必须改变。
  • 正文任意字符改变,稳定摘要必须改变。
  • 保留型 query 改变,稳定摘要必须改变。
  • 规范化执行两次与执行一次结果相同。

失败恢复怎么做

即使摘要稳定,发布结果未知时也不能重放批准。正确顺序仍是:先读草稿或目标对象的当前状态;若已经生成文章 ID,就验证审核页或公开页;只有明确未提交,才基于当前 revision 重新批准。

这能防止两个相反的问题:签名刷新造成假冲突,以及网络超时造成重复提交。

收束

内容指纹不是纯技术细节,而是业务身份的可执行定义。设计它时最该问的不是"用什么哈希",而是"哪些变化代表换了对象,哪些只是访问凭证刷新"。

一个可靠答案通常是双哈希加窄规范化:严格证据不丢,业务批准不被临时签名误伤,任何域名、对象路径和正文变化仍然 fail closed。

发布前门禁

  • 重点呈现方案冲突与取舍
  • 保留严格 SHA 与稳定 SHA 两套证据
  • 对域名、对象路径和正文变化保持拒绝
  • 未把单个平台现象夸大为所有 CDN 的统一规则
相关推荐
默_笙14 小时前
🍙 给每个请求过安检:FastAPI 是怎么把校验写进类型注解的
python
qq_4260039614 小时前
启动playwright录制codegen生成自动化测试脚本
python·自动化
虎头金猫14 小时前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
长沙三为智能科技15 小时前
家政小程序开发从0到上线:五阶段交付流程与验收清单
python
伞伞悦读15 小时前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
只睡四小时16 小时前
Canvas 弹道联机实战:700 行 + 固定时间步长
python·websocket·html5·游戏开发·canvas
龙亘川16 小时前
一网统管AI平台民生业务实践:基于城市数字底座赋能公积金业务服务升级
大数据·安全·智慧城市·开源软件·数据可视化·政务
奇思妙想聪明勤奋的小羊16 小时前
DeepAgents第5章:子Agent 与上下文隔离—让 Agent学会委派
人工智能·python·学习·语言模型
lpfasd12317 小时前
2026年第38周GitHub趋势周报
python·科技·github