步骤 1 — 注册您的公钥(一次性设置)
生成 4096 位 RSA 密钥对,并与 Zonos 共享公钥。这在入职期间进行一次。
生成密钥对
1
# Generate private key2
openssl genrsa -out private_key.pem 40963
4
# Extract public key5
openssl rsa -in private_key.pem -pubout -out public_key.pem将 public_key.pem 分享给 Zonos。将 private_key.pem 存储在专用的密钥管理器中(如 AWS Secrets Manager、HashiCorp Vault 等)— 从不在源代码控制或环境变量中。
Zonos 将注册您的密钥并返回您的 Organization ID,这将成为所有 JWT 断言中的 iss 声明。
步骤 2 — 构建 JWT 断言
使用您的私钥用 RS256 对 JWT 进行签名。该断言对单次令牌交换有效 — 使过期时间窗口保持较短(60–300 秒)。
必需的声明
| 声明↕ | 值↕ |
|---|---|
iss | 您的 Zonos Organization ID(例如 "org_abc123") |
sub | 标识调用服务(例如 "checkout-service") |
aud | 必须完全为 "zonos-auth" |
exp | Unix 时间戳;从 iat 起 60–300 秒 |
iat | 发行的 Unix 时间戳 |
jti | 每个断言的唯一 UUID(启用重放检测) |
JWT 头必须指定 "alg": "RS256" 和 "typ": "JWT"。
代码示例
1
import jwt, uuid, time2
3
with open("private_key.pem") as f:4
private_key = f.read()5
6
now = int(time.time())7
assertion = jwt.encode(8
{9
"iss": "org_abc123",10
"sub": "checkout-service",11
"aud": "zonos-auth",12
"iat": now,13
"exp": now + 300,14
"jti": str(uuid.uuid4()),15
},16
private_key,17
algorithm="RS256",18
)步骤 3 — 交换断言以获取访问令牌
将签名的 JWT 发送到 Zonos 令牌端点以接收短期 Bearer 令牌。
端点
POST https://auth.zonos.com/oauth/token
Content-Type: application/json
也接受 application/x-www-form-urlencoded。
请求字段
| 字段↕ | 必需↕ | 值↕ |
|---|---|---|
grant_type | 是 | "urn:ietf:params:oauth:grant-type:jwt-bearer" |
assertion | 是 | 您的签名 JWT(紧凑序列化) |
请求和响应
1
{2
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",3
"assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."4
}| 响应字段↕ | 描述↕ |
|---|---|
access_token | 用于所有后续 API 请求的 Bearer 令牌 |
token_type | 始终为 "Bearer" |
expires_in | 到期前的秒数(默认值:300) |
scope | 授予此令牌的以空格分隔的权限 |
完整代码示例
1
import requests2
3
response = requests.post(4
"https://auth.zonos.com/oauth/token",5
json={6
"grant_type": ,7
: assertion,8
},9
)10
data = response.json()11
access_token = data[]12
expires_in = data[]步骤 4 — 使用访问令牌调用 Zonos API
在 Authorization 头中的 Bearer 令牌形式包含访问令牌,适用于每个 Zonos API 请求。
示例请求
1
curl -X POST https://api.zonos.com/graphql \2
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \3
-H "Content-Type: application/json" \4
-d '{ "query": "{ ... }" }'令牌生命周期和缓存
访问令牌默认在 5 分钟后过期。缓存令牌并主动刷新 — 不要在每个 API 调用上请求新令牌。每次刷新都需要一个新签名的 JWT 断言。
1
import time, requests2
3
_cache = {"access_token": None, "expires_at": 0}4
5
def get_access_token():6
if time.time() < _cache["expires_at"] - 30:7
return _cache["access_token"]8
9
assertion = build_jwt_assertion()10
data = requests.post(11
"https://auth.zonos.com/oauth/token",12
json={13
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",14
"assertion": assertion,15
},16
).json()17
18
_cache["access_token"] = data["access_token"]19
_cache["expires_at"] = time.time() + data["expires_in"]20
return _cache["access_token"]错误参考
所有错误遵循 OAuth 2.0 错误响应格式 (RFC 6749 §5.2):
1
{2
"error": "invalid_grant",3
"error_description": "JWT assertion has expired"4
}| HTTP 状态↕ | error↕ | 原因↕ |
|---|---|---|
400 | unsupported_grant_type | grant_type 不是 urn:ietf:params:oauth:grant-type:jwt-bearer |
400 | invalid_request | 缺少或格式不正确的字段 |
401 | invalid_grant | 无效签名、过期的断言、未知组织或未注册的密钥 |
500 | server_error | 内部错误 — 如果持续存在,请联系 Zonos 支持 |
常见 invalid_grant 原因:
exp在过去 — 确保您的系统时钟已 NTP 同步aud不完全是"zonos-auth"iss与您的注册 Organization ID 不匹配- 公钥已轮换,但尚未与 Zonos 更新
安全最佳实践
- 保护您的私钥。 将其存储在专用的密钥管理器中 — 从不在源代码控制、环境变量或日志中。
- 保持断言短期。 60–300 秒是标准的;没有理由发行更长期的断言。
- 包含
jti。 每个断言的唯一值支持服务器端重放检测。 - 定期轮换密钥对。 在撤销旧密钥对之前向 Zonos 注册新公钥以避免停机。
- 从不记录
access_token或assertion值。 将两者都视为凭证。
OAuth 2.0 身份验证
使用非对称密钥加密对您的后端服务进行身份验证 — 无需共享密钥。
Zonos 支持通过 OAuth 2.0 JWT Bearer Token Grant (RFC 7523) 进行机器到机器的身份验证。您的服务使用 RSA 私钥对短期 JWT 进行签名;Zonos 使用您注册的公钥验证它,并返回作用于您的组织的 Bearer 令牌。
流程概述:
Authorization: Bearer <token>。