HMAC-SHA256是当前最稳妥的签名验证算法:带密钥、抗长度扩展、Python原生支持;需结合时间戳窗口校验与Redis缓存nonce防重放,并统一签名原文格式。

签名验证该用什么算法:HMAC-SHA256 是当前最稳妥的选择
Django 自身不提供开箱即用的 API 签名机制,必须自己实现。选算法时别纠结 MD5 或 SHA1——它们已被证明不安全;hashlib.md5 和 hashlib.sha1 在签名场景下应直接排除。HMAC-SHA256 是事实标准:它带密钥、抗长度扩展、Python 原生支持且性能足够。
- Django 项目里统一用
hmac.new+hashlib.sha256,不要手写拼接字符串再哈希 - 密钥(
SECRET_KEY)不能硬编码在视图里,应从环境变量或 Django 的settings.SECRET_API_KEY读取 - 签名原文必须包含时间戳(
timestamp)、随机串(nonce)、HTTP 方法、完整路径、查询参数(按字典序排序)、请求体(仅当 Content-Type 为application/json时取request.body原始字节)
import hmac
import hashlib
import json
<p>def generate_signature(payload_dict, secret_key: bytes) -> str:</p><h1>payload_dict 包含 method, path, query_string, timestamp, nonce, body_bytes(可选)</h1><pre class='brush:python;toolbar:false;'>msg = f"{payload_dict['method']}\n{payload_dict['path']}\n{payload_dict['query_string']}\n{payload_dict['timestamp']}\n{payload_dict['nonce']}"
if 'body_bytes' in payload_dict:
msg += f"\n{payload_dict['body_bytes'].decode('utf-8') if isinstance(payload_dict['body_bytes'], bytes) else ''}"
sig = hmac.new(secret_key, msg.encode('utf-8'), hashlib.sha256).digest()
return base64.b64encode(sig).decode('ascii')防重放的关键不是加时间戳,而是校验窗口期+缓存 nonce
只传 timestamp 参数没用——攻击者可以截获一次合法请求,改个毫秒数再发。真正起作用的是两件事:服务端校验 timestamp 是否落在允许窗口内(比如 ±300 秒),同时用 Redis 缓存已用过的 nonce 值并设置过期时间(必须 ≤ 窗口时长)。
- 时间窗口建议设为 300 秒,太短影响弱网用户,太长增加重放风险
-
nonce必须全局唯一(可用 UUID4),且每次请求都不同;Redis key 形如f"api:nonce:{nonce}",value 可设为任意值,EX 设为 300 - 校验顺序必须是:先检查
timestamp是否超窗 → 再查nonce是否已存在 → 都通过才继续解析签名 - 不要用数据库存 nonce:高并发下写入延迟和锁竞争会拖垮接口
Django 中间件怎么拦截并验证签名:别在每个视图里重复写逻辑
把签名验证逻辑塞进视图函数里,等于给每个接口埋雷。正确做法是写一个可配置的中间件,在请求进入视图前完成校验,并提前返回 401/403。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
- 中间件需支持白名单路径(如
/health/、/docs/),避免对静态资源或登录接口强制验签 - 从请求头读签名字段(推荐
X-Signature),从 QueryString 读timestamp和nonce,拒绝缺失任一参数的请求 - 请求体如果是 JSON,必须用
request.body原始字节参与签名计算;若已触发request.POST或request.data,body 可能被消费过,导致验签失败 - 验签失败时,直接
return HttpResponse(status=401),不要抛异常——Django 异常会走错误页流程,暴露堆栈且无法控制响应格式
客户端和服务端签名不一致?90% 是这几个细节没对齐
调试阶段最常见的问题是「明明按文档写了,但签名总不匹配」。根本原因往往不是算法错,而是数据预处理不一致:
- 查询参数排序:服务端用
sorted(request.GET.items()),客户端必须严格按 key 字典序拼接,空值也要保留(如a=&b=1≠b=1&a=) - 路径是否带 query string:签名中的
path应为/api/v1/user,不含?a=1&b=2,那部分必须单独提取为query_string并排序 - 时间戳单位:服务端用
int(time.time())(秒级),客户端必须同步用秒,不能传毫秒(Date.now()要除以 1000 并取整) - 字符编码:所有字符串参与签名前统一转 UTF-8 字节,JSON body 尤其注意——Python 的
json.dumps(..., separators=(',', ':'))去空格,否则客户端 JSJSON.stringify()默认带空格会导致不一致
签名验证本身不难,难的是两端对「签名原文」的定义完全一致。只要有一处空格、换行、排序、编码、单位不匹配,结果就全错。上线前务必用固定测试用例(hardcode timestamp/nonce/body)跑通双向签名比对。

















