API 签名机制 全解

从原理到实战,彻底搞懂 API 签名如何保护你的接口,以及它与 JWT 的本质区别

HMAC-SHA256 防篡改 / 防重放 物联网场景选型

什么是 API 签名机制

API 签名(API Signature)是一种基于密钥的请求认证与完整性校验机制。客户端在发送请求前,使用预共享密钥(Secret Key)对请求的关键参数进行加密运算,生成一串不可逆的签名值,随请求一起发送;服务端收到后用同样的密钥和算法重新计算签名,比对两者是否一致来验证请求的合法性。

核心思想

签名本身不加密数据内容,而是为数据生成一个"数字指纹"。任何对数据的篡改都会导致指纹不匹配,从而被服务端拒绝。

签名前
POST /api/device/data
timestamp=1700000000
device_id=sensor_01
temperature=23.5
签名后
POST /api/device/data
timestamp=1700000000
device_id=sensor_01
temperature=23.5
sign=a3f8c2...e7d1

API 签名解决什么问题

HTTP 协议本身是明文传输,没有任何身份校验和完整性保护。如果不加签名,API 将面临以下威胁:

请求篡改

攻击者截获请求后修改参数(如把转账金额从 100 改为 10000),服务端无法识别数据已被篡改

重放攻击

攻击者截获合法请求后原样重发,如重复提交订单、重复执行设备控制指令

身份伪造

任何人只要知道 API 地址就能调用,无法确认请求者的真实身份

🔒

密钥泄露风险

如果直接在请求中传递密钥,一旦被抓包密钥即泄露。签名机制中密钥不参与传输

签名机制如何应对

防篡改

签名由请求参数 + 密钥共同计算,改任何参数签名都会变

防重放

加入 timestamp + nonce,服务端校验时间窗口和唯一性

身份认证

AccessKey 标识身份,SecretKey 参与签名,密钥不传输

密钥安全

HMAC 单向运算,密钥永远不出现在网络传输中

签名机制的工作过程

以最常见的 HMAC-SHA256 签名方案为例,完整流程如下:

1

构造签名串(StringToSign)

将请求方法、路径、时间戳、关键参数按固定规则拼接成待签名字符串

HTTP方法\n路径\n时间戳\n参数排序拼接
2

HMAC-SHA256 运算

使用预共享的 Secret Key 作为密钥,对签名串做 HMAC-SHA256 运算,得到二进制摘要

hmac_sha256(secret_key, string_to_sign)
3

Base64 / Hex 编码

将二进制摘要编码为可传输的字符串形式,作为 sign 参数

sign = base64(hmac_digest)
4

附加签名到请求

access_keytimestampsignnonce 放入请求头或查询参数

X-Access-Key / X-Timestamp / X-Sign / X-Nonce
5

服务端验签

服务端用 access_key 查出对应的 secret_key,用同样算法重算签名,比对是否一致;同时校验 timestamp 时间窗口和 nonce 唯一性

签名串构造规则

不同云厂商规则不同,但核心思路一致——把所有影响请求语义的字段纳入签名,确保任何篡改都能被检测到:

// 通用签名串格式
string_to_sign = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + SORTED_QUERY_PARAMS
// 示例
sign_str = "POST\n/api/v1/data\n1700000000\ndevice_id=sensor_01&temperature=23.5"

防重放三重保障

Timestamp 时间戳

服务端校验请求时间与当前时间的差值(如 ±5 分钟),过期请求直接拒绝

Nonce 随机数

每次请求携带唯一随机串,服务端缓存近期 nonce 拒绝重复值

签名绑定

timestamp 和 nonce 参与签名计算,攻击者无法替换它们而不破坏签名

签名实现代码示例

Python — 客户端签名
import hmac, hashlib, time, uuid, urllib.parse # 预共享密钥对(由平台分发) ACCESS_KEY = "ak_device_01" SECRET_KEY = "sk_9f8e7d6c5b4a3210" def sign_request(method, path, params): """为 HTTP 请求生成 HMAC-SHA256 签名""" # 1. 加入 timestamp 和 nonce params["timestamp"] = str(int(time.time())) params["nonce"] = uuid.uuid4().hex[:16] # 2. 参数按 key 字典序排列并 URL 编码 sorted_params = sorted(params.items(), key=lambda x: x[0]) query_string = urllib.parse.urlencode(sorted_params) # 3. 构造签名串 string_to_sign = f"{method}\n{path}\n{query_string}" # 4. HMAC-SHA256 签名 signature = hmac.new( SECRET_KEY.encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha256 ).hexdigest() # 5. 返回带签名的请求头 return { "X-Access-Key": ACCESS_KEY, "X-Timestamp": params["timestamp"], "X-Nonce": params["nonce"], "X-Sign": signature, } # 使用示例 headers = sign_request( method="POST", path="/api/v1/device/data", params={"device_id": "sensor_01", "temperature": "23.5"} ) print(headers) # {'X-Access-Key': 'ak_device_01', 'X-Timestamp': '1700000000', # 'X-Nonce': 'a1b2c3d4e5f67890', 'X-Sign': 'e4d2c1...a8f3'}
Node.js — 客户端签名
const crypto = require("crypto"); // 预共享密钥对 const ACCESS_KEY = "ak_device_01"; const SECRET_KEY = "sk_9f8e7d6c5b4a3210"; function signRequest(method, path, params) { // 1. 加入 timestamp 和 nonce params.timestamp = Math.floor(Date.now() / 1000).toString(); params.nonce = crypto.randomBytes(8).toString("hex"); // 2. 参数按 key 排序并拼接 const sortedKeys = Object.keys(params).sort(); const queryString = sortedKeys .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`) .join("&"); // 3. 构造签名串 const stringToSign = `${method}\n${path}\n${queryString}`; // 4. HMAC-SHA256 签名 const signature = crypto .createHmac("sha256", SECRET_KEY) .update(stringToSign) .digest("hex"); // 5. 返回请求头 return { "X-Access-Key": ACCESS_KEY, "X-Timestamp": params.timestamp, "X-Nonce": params.nonce, "X-Sign": signature, }; } // 使用 const headers = signRequest("POST", "/api/v1/device/data", { device_id: "sensor_01", temperature: "23.5" }); console.log(headers);
Python — 服务端验签(FastAPI)
from fastapi import FastAPI, Request, HTTPException from functools import wraps import hmac, hashlib, time app = FastAPI() # 模拟数据库:access_key → secret_key 映射 KEY_STORE = {"ak_device_01": "sk_9f8e7d6c5b4a3210"} # 已使用的 nonce 缓存(生产环境用 Redis,设 TTL=5min) used_nonces = set() def verify_signature(request: Request): """中间件:验证请求签名""" ak = request.headers.get("X-Access-Key") ts = request.headers.get("X-Timestamp") nonce = request.headers.get("X-Nonce") sign = request.headers.get("X-Sign") # 1. 检查必要字段 if not all([ak, ts, nonce, sign]): raise HTTPException(401, "缺少签名参数") # 2. 查找 secret_key sk = KEY_STORE.get(ak) if not sk: raise HTTPException(401, "无效的 AccessKey") # 3. 校验时间窗口(±300秒 = 5分钟) if abs(time.time() - int(ts)) > 300: raise HTTPException(401, "请求已过期") # 4. 校验 nonce 唯一性(防重放) if nonce in used_nonces: raise HTTPException(401, "请求已被使用") used_nonces.add(nonce) # 5. 重算签名并比对 # (此处简化,实际需要解析 params 重新拼接签名串) expected = hmac.new( sk.encode(), "...".encode(), hashlib.sha256 ).hexdigest() if not hmac.compare_digest(sign, expected): raise HTTPException(401, "签名验证失败") @app.post("/api/v1/device/data") async def upload_data(request: Request): verify_signature(request) # 签名验证通过,处理业务逻辑... return {"status": "ok"}

API 签名 vs JWT:本质区别

两者都能做身份认证,但设计哲学和适用场景截然不同。理解差异的关键在于:签名是"请求级"验证,JWT 是"会话级"凭证

核心差异一句话

API 签名

每次请求都要重新计算签名,签名值随请求内容变化。验证的是"这个请求是否被篡改"

JWT

登录时颁发 Token,后续请求携带即可,Token 内容固定。验证的是"你是谁、你有什么权限"

维度 API 签名 (HMAC) JWT
核心用途 请求完整性 + 身份认证 身份声明 + 权限传递
密钥持有 双方持有对称密钥 签发方私钥 / 验证方公钥
每次请求计算 是,必须重算 否,Token 固定
防篡改 强,参数级绑定 仅 Token 本身防篡改
防重放 内置(timestamp+nonce) 需额外机制(exp/jti)
是否自包含 否,服务端需查密钥 是,Payload 自带声明
状态管理 无状态(但 nonce 需存储) 有状态(吊销需黑名单)
计算开销 HMAC 很轻 RSA/ECDSA 较重
适用客户端 设备 / 服务端(可保密密钥) 浏览器 / App(不能保密密钥)
密钥轮换 需同步更新双方 仅签发方更新
典型场景 AWS API、阿里云、IoT 设备上报 用户登录、SSO、微服务间鉴权

一句话总结区别

API 签名是"对每个请求做不可伪造的指纹",JWT 是"一张可以反复出示的身份证"。前者保护请求不被篡改和重放,后者证明持有者的身份和权限。两者可以组合使用:JWT 管用户身份,签名管请求完整性。

物联网设备平台用什么方案

物联网场景有其独特约束:设备资源受限、网络不稳定、设备数量巨大、安全要求高。选型时需要特别考虑这些因素。

10万+
设备规模
KB级
设备 RAM
MQTT
主流协议
高危
物理安全风险

方案对比

JWT 认证

设备登录获取 Token,后续请求携带 Token。

  • Token 固定不变,无法绑定单个请求的参数
  • RSA/ECDSA 签名验证开销高于 HMAC
  • Token 过期前无法有效吊销(需黑名单机制)
  • Payload 自包含,设备端解析方便
  • 适合设备→平台的长会话场景(如 MQTT 连接鉴权)

主流物联网平台的实际选择

平台HTTP APIMQTT 连接签名算法
AWS IoT CoreSigV4 签名签名 + TLS 双向认证HMAC-SHA256
阿里云 IoT签名机制签名 + MQTT PasswordHMAC-SHA256 / SM3
腾讯云物联网签名机制签名 + MQTT PasswordHMAC-SHA256 / SHA1
Azure IoT HubSAS Token (类签名)SAS Token + X.509HMAC-SHA256
EMQXJWT / 签名可选JWT / 签名可选灵活配置

物联网场景结论

HTTP/CoAP 数据上报 → HMAC 签名(每个请求独立验证,防篡改防重放)
MQTT 长连接 → 签名 + TLS 双向认证(连接时签名鉴权,运行中 TLS 保护)
JWT 仅作辅助,适用于需要跨服务传递身份声明的场景,不作为设备主认证手段。

典型架构:签名 + TLS 组合方案

// 设备上报数据 — 完整安全分层
第1层 TLS — 传输加密,防止窃听和中间人
第2层 HMAC 签名 — 请求完整性 + 设备身份 + 防重放
第3层 业务权限 — 基于设备标识的 Topic/资源 ACL
// 缺一不可:TLS 保通道安全,签名保请求可信,ACL 保权限边界

交互式签名演示

在下方模拟一次完整的 API 签名流程,理解每个步骤的实际输出:

点击「生成签名」查看完整流程...

篡改检测实验

用上面的参数生成签名后,尝试修改下方参数看签名是否还能匹配:

先生成原始签名,再验证篡改...
本文档由 WorkBuddy 生成 | 最后更新:2026-05-25 | HMAC-SHA256 签名方案适用于物联网设备平台