Flask接口签名验证需在视图函数内统一提取原始请求数据(URL参数字典序拼接+原始body字节+timestamp+nonce),用HMAC-SHA256计算并hmac.compare_digest校验,失败立即abort(401)。

Flask 接口怎么加签名验证
直接在视图函数里做校验,别依赖中间件或装饰器堆砌逻辑——签名验证必须和业务参数强绑定,否则容易漏掉 request.args 或 request.json 里的关键字段。
常见错误是只对 request.json 签名,但实际请求可能走 application/x-www-form-urlencoded,或者混合了 URL 参数和 body。必须统一提取原始字节再哈希。
- 用
request.get_data(cache=True, as_text=False)拿原始 body,避免 Flask 自动解析破坏顺序 - URL 参数要按字典序拼接(
sorted(request.args.items())),不能直接用request.url,因为可能含无关 query(如跟踪参数) - 签名字段必须包含时间戳
timestamp和随机数nonce,两者都参与签名计算,且服务端需校验timestamp是否在 5 分钟内 - 密钥不硬编码,从环境变量读取:
os.environ.get("API_SECRET_KEY")
签名算法选 HMAC-SHA256 还是 RSA
HMAC-SHA256 更合适:Flask 是服务端单点部署,不需要非对称加密的分发优势;RSA 签名校验慢、实现易出错(比如私钥格式、padding 方式),还容易被绕过(如传空 signature 字段跳过验证)。
性能上,HMAC 在 Python 里用 hmac.new() 足够快,10k QPS 下 CPU 开销可忽略;而 RSA 的 verify() 在 PyCryptodome 里默认做完整 PKCS#1 v1.5 校验,失败时抛异常,但错误信息可能暴露签名结构。
立即学习“Python免费学习笔记(深入)”;
- 务必用
hmac.compare_digest()对比签名,防止时序攻击 - 签名原文格式固定为:
f"{sorted_params_str}&{body_bytes.hex()}&{timestamp}&{nonce}",其中sorted_params_str是"k1=v1&k2=v2"形式 - 不要 base64 encode 后再 hex,直接
hmac.new(...).digest().hex(),减少编解码误差
前端传签名时容易漏掉的三个字段
前端 JS 发请求时,常以为只要塞个 X-Signature header 就行,结果后端校验总失败——问题往往出在没同步传全签名依据字段。
这三个字段必须作为普通请求参数(query 或 body)显式传递,不能只放 header 里:
-
timestamp:毫秒级时间戳,前后端用同一时区(推荐 UTC) -
nonce:每次请求唯一字符串(可用uuid4().hex[:16]),服务端需缓存最近 100 个做重复请求拦截 -
sign_method:固定值"HMAC-SHA256",方便未来扩展其他算法,也避免前端误用 MD5
如果用 axios,记得在 params 或 data 里写明它们,而不是只塞进 headers。
Flask 中怎么安全地校验签名并提前中断
校验必须在业务逻辑执行前完成,且失败时直接返回 401,不能继续往下走——否则可能触发数据库查询或日志记录,反而暴露接口行为。
别用 try/except 包裹整个视图,而是单独抽一个 verify_request_signature() 函数,在 request context 里调用,校验失败就 abort(401)。
- 检查
request.headers.get("X-Signature")是否存在,为空直接 401 - 从
request.args和request.form合并参数时,注意request.json可能为 None,要用request.get_json(silent=True) - 生成服务端签名后,用
hmac.compare_digest(sig_from_header, server_sig),不用== - 校验通过后再解析 JSON 或读取 form,避免无效请求消耗解析资源
最麻烦的其实是时钟不同步——别依赖 NTP 自动校准,上线前用 ntpq -p 看服务器时间偏差,超过 30 秒就手动 ntpdate 一次。这个点很容易被忽略,但会导致所有签名突然失效。


















