Python REST API验签核心是服务端复现签名逻辑并比对,关键在密钥管理、时间戳校验(≤5分钟)和参数归一化;X-Signature需用hmac.compare_digest安全比对,签名原文须按字典序排序参数、JSON键排序且无空格,密钥严禁硬编码或日志泄露。

直接上结论:Python REST API 的请求验签机制,核心是服务端复现客户端签名逻辑,并比对结果;关键不在“怎么加”,而在“密钥怎么管、时间戳怎么校验、参数怎么排序归一化”。
验签时必须校验 X-Signature 和 X-Timestamp
只验签名不验时间戳,等于没验——重放攻击分分钟绕过。客户端需在请求头带上 X-Timestamp(毫秒级 UNIX 时间戳)和 X-Signature(如 HMAC-SHA256 签名值)。
-
X-Timestamp必须和服务端当前时间差 ≤ 300000ms(5 分钟),超出即拒收 -
X-Signature是对「排序后的查询参数 + 请求体(若为 JSON)+ 时间戳字符串」拼接后,用共享密钥HMAC-SHA256计算的 hex 值 - 服务端必须用
int(time.time() * 1000)获取毫秒时间,不能用datetime.now().timestamp()(精度和时区易出错)
Flask 中实现验签中间件要避开 body 读取陷阱
Flask 的 request.get_data() 默认会消耗一次原始 body 流,后续再调用会返回空字节;而验签需要完整 body 内容(尤其 POST/PUT 的 JSON),必须提前缓存。
- 在装饰器或 before_request 钩子中,先执行
raw_body = request.get_data(cache=True) - 解析 JSON 时用
json.loads(raw_body),而非request.get_json()(后者可能触发二次读取) - 若接口同时支持表单和 JSON,统一用
raw_body+request.args.to_dict()构建待签名字符串,避免request.form和request.json混用导致参数遗漏
hmac.compare_digest() 是唯一安全的签名比对方式
用 == 直接比较签名字符串会引发时序攻击——攻击者可通过响应延迟反推签名字符。Python 标准库提供了恒定时间比较函数。
立即学习“Python免费学习笔记(深入)”;
- 必须用
hmac.compare_digest(sig_from_header, expected_sig),而不是sig_from_header == expected_sig -
expected_sig必须是 bytes 类型,hmac.new(...).hexdigest().encode()是常见写法 - 如果客户端传的是 base64 编码的签名,服务端需先
base64.b64decode()再比对,且 decode 后也必须用compare_digest
签名原文构造最容易漏掉的三个细节
签名原文不是简单拼接,而是有严格顺序和格式要求,漏掉任意一项都会导致验签失败。
- 查询参数必须按 key 字典序升序排列,value 不做 URL 解码(保持原始编码,如
a%20b就是a%20b) - POST/PUT 的 JSON body 必须用
json.dumps(data, separators=(',', ':'), sort_keys=True)序列化(去空格、键排序) - 最终签名原文格式为:
f"{sorted_query_string}&{json_body_str}&{timestamp}",三部分用&连接,无额外空格或换行
最常被忽略的是:开发环境用硬编码密钥调试没问题,上线后密钥必须从环境变量或密钥管理服务加载,且绝不能出现在日志里——哪怕只是调试时打印了 hmac.new(...).digest() 的中间结果,也会导致密钥泄露风险陡增。


















