必须结合时间戳与nonce、Redis原子操作及请求体签名,否则重放攻击仍可利用;服务端需校验时间戳在[now−300, now+10]内且nonce未重复,中间件须安全缓存request.body并标准化签名原文。

直接上结论:只校验时间戳或只存 nonce 都不够,必须两者结合 + Redis 原子操作 + 请求体参与签名,否则重放攻击在 5 分钟窗口内依然可被批量利用。
为什么单纯用 time.time() 校验时间戳会失效
服务端用 abs(time.time() - timestamp) > 300 判断,看似安全,但实际有三个致命漏洞:
- 客户端系统时间可任意篡改,攻击者把本地时间调慢 10 分钟再发包,签名仍有效
- 服务端没做“未来时间”拦截,攻击者设一个远大于当前时间的
timestamp,只要不超窗口就一直能用 - 多个请求共用同一组
timestamp+nonce(比如前端没重生成),Redis 缓存未命中就放行
正确做法是:服务端必须同时检查 timestamp 是否在 [now−300, now+10] 范围内(允许未来 10 秒容错),且该 timestamp 不能和已缓存的 nonce 组合重复。
Django 中间件里怎么安全读取并缓存 request.body
request.body 是单次可读流,中间件里一读,后续 DRF 的 request.data 就为空。常见错误是直接写 body = request.body.decode() 后丢弃原始 bytes。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 必须先缓存原始
bytes:用request._cached_body = request.body - 再重置
request._body为None,让 DRF 解析器重新读取 - 对
application/json类型,签名原文中 body 必须用json.dumps(dict(sorted(json.loads(body).items())), separators=(',', ':'))排序后标准化,避免 key 顺序不同导致签名不一致 - 跳过
multipart/form-data—— 不读request.body,改用request.POST.dict()+request.FILES.keys()构造参数签名
Redis 存 nonce 为什么必须用 SETNX + EXPIRE 或 SETEX
用 redis.set("nonce:abc", "1", ex=600) 看似简单,但存在竞态条件:两次并发请求都查不到 key,都写入成功,导致一次 nonce 被接受两次。
- 必须用原子命令:
redis.setex("nonce:abc", 600, "1")或redis.set("nonce:abc", "1", ex=600, nx=True) - key 命名建议带 app_id:
f"nonce:{app_id}:{nonce}",防止不同接入方冲突 - 不要用
INCR或GETSET—— 它们无法保证“不存在才设值+设过期”原子性 - 校验失败时返回 HTTP 401,且不记录日志中的
nonce和timestamp,防信息泄露
签名原文拼接顺序和字段缺一不可
签名不是为了加密,是为了绑定请求上下文。漏掉任意一项,攻击者就能构造等效请求绕过校验。
- 必须包含:
method(大写) +path(原始路径,不含 query string) +timestamp(整数秒) +nonce(字符串) +sorted_query_string(urllib.parse.urlencode(sorted(request.GET.items()), doseq=True)) +normalized_body_hash(hashlib.sha256(body_bytes).hexdigest()) - 拼接分隔符统一用
|,例如:f"POST|/api/order|{ts}|{nonce}|{query}|{body_hash}" - 密钥必须从数据库查 App 对应的
app_secret,不能用 Django 的SECRET_KEY,且查库结果必须加cache.get_or_set缓存 5 分钟
最易忽略的是:签名验证中间件必须放在 CsrfViewMiddleware 之后、所有业务视图之前;且对 /api/login、/healthz 这类免签接口,要用路径白名单明确跳过,否则登录本身就被拦住。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










