鉴权说明
除公开端点外,所有请求需使用 ak / sk 做 HMAC-SHA256 签名。 本页头名与规则以服务端实现为准。
请求头
注意头名:本平台头名一律带
X-Gstack- 前缀,请严格按下表名称发送;沿用其它平台或旧文档的头名写法将返回 401 SIGN_MISSING。 | 请求头 | 必填 | 说明 |
|---|---|---|
X-Gstack-Ak | 是 | API Key 标识(ak),公开标识,可与请求一同记录。 |
X-Gstack-Timestamp | 是 | Unix 秒(十进制字符串),与服务器偏差需 ≤ 5 分钟。 |
X-Gstack-Nonce | 是 | 一次性随机串,长度 ≥ 16,同一 ak 下不可重复。 |
X-Gstack-Signature | 是 | hex(HMAC-SHA256(sk, 待签串)),小写 hex。 |
X-Gstack-Sign-Version | 否 | 签名版本,缺省 v1;存在时必须为 v1。 |
待签串规则(stringToSign)
5 段,以换行符 \n 连接, 末尾无换行。签名 = hex(HMAC-SHA256(sk, stringToSign))(小写)。
stringToSign
METHOD
PATH
TIMESTAMP
NONCE
hex(SHA256(body))| # | 段 | 说明 |
|---|---|---|
| 1 | METHOD | HTTP 方法,**大写**(如 GET / POST) |
| 2 | PATH | 完整请求路径(如 /api/gstack/v1/whoami),不含 query |
| 3 | TIMESTAMP | 与请求头 X-Gstack-Timestamp 逐字节相同(Unix 秒,不重排格式) |
| 4 | NONCE | 与请求头 X-Gstack-Nonce 逐字节相同(≥16 字符,一次性) |
| 5 | BODY_SHA256 | hex(SHA-256(原始 body 字节)),小写;空 body 为固定值 |
空 body 的 SHA-256 固定为 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855。
时间窗与 nonce
- · 时间窗:
|服务器时间 − TIMESTAMP| ≤ 5 分钟,超出即拒绝(请校准时钟)。 - · nonce 一次性:验签通过后消费,同一 ak + 同一 nonce 再次使用即拒绝(防重放)。
- ·
PATH必须是完整请求路径(含/api/gstack/v1/...), 不含 query(query 不参与签名)。 - ·
TIMESTAMP/NONCE与请求头逐字节相同,不做重格式化。
可粘贴示例
以下示例调用 GET /api/gstack/v1/whoami,与 快速开始 一致,可原样跑通。
python3(仅标准库)
import hashlib, hmac, os, time, urllib.request
AK = "ak_live_在此填入你的 AK"
SK = "sk_在此填入你的 SK"
BASE = "https://api.nanniwan.com"
PATH = "/api/gstack/v1/whoami" # 完整路径,不含 query
body = b"" # GET,无 body
ts = str(int(time.time())) # Unix 秒
nonce = os.urandom(16).hex() # 长度 >= 16
body_sha = hashlib.sha256(body).hexdigest()
string_to_sign = "GET\n" + PATH + "\n" + ts + "\n" + nonce + "\n" + body_sha # 5 段,无尾随换行
signature = hmac.new(SK.encode(), string_to_sign.encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request(BASE + PATH, method="GET")
req.add_header("X-Gstack-Ak", AK)
req.add_header("X-Gstack-Timestamp", ts)
req.add_header("X-Gstack-Nonce", nonce)
req.add_header("X-Gstack-Signature", signature)
req.add_header("X-Gstack-Sign-Version", "v1")
with urllib.request.urlopen(req) as resp:
print(resp.status, resp.read().decode())失败排查
失败返回真实 HTTP 状态码,响应体 data.reason 为机器可读原因码。
| HTTP | reason | 文案 | 常见原因 |
|---|---|---|---|
| 401 | SIGN_MISSING | 缺少签名 | 四头(Ak/Timestamp/Nonce/Signature)未齐备,或 nonce 长度 <16 |
| 401 | SIGN_EXPIRED | 请求已过期,请校准时钟 | 时间戳与服务器偏差超过 ±5 分钟(含时间戳非整数) |
| 401 | SIGN_MISMATCH | 签名校验失败 | 待签串或 sk 与实现不一致(含 path 带 query、body 未参与签名) |
| 401 | NONCE_REPLAYED | 重复请求 | 同一 ak + nonce 被重复使用(nonce 一次性) |
| 401 | AK_INVALID | 凭据无效 | X-Gstack-Ak 不存在 |
| 401 | AK_REVOKED | 凭据已失效 | 该 ak 已被吊销(status=0) |
| 401 | SIGN_VERSION_UNSUPPORTED | 签名版本不受支持 | X-Gstack-Sign-Version 存在且 ≠ v1 |
| 413 | BODY_TOO_LARGE | 请求体过大 | body 超过服务端上限 |
| 400 | SUBJECT_OVERRIDE_FORBIDDEN | 请求包含禁止的主体标识 | body/query 中出现 merchant_id/user_id/tenant_id/subject_id 等禁止键(主体只从 Key 推导) |
| 503 | NONCE_STORE_UNAVAILABLE | 服务暂不可用,请稍后重试 | nonce 存储(Redis)不可用 —— 按拒绝处理(fail-closed) |
密钥安全须知
- · sk 只在领取时显示一次,平台不提供二次查看;请立即保存到安全位置。
- · 不要把 sk 写进前端代码、移动端 App、公开仓库或客户端调试日志——签名必须在服务端完成。
- · claimSecret 是管理该 Key 的唯一凭证,请像保管 sk 一样保管;领取 / 重置后会轮换,需重新保存。
- · sk 泄露时,请尽快在「密钥管理」页重置或吊销该凭据。