本指南涵盖 API Key 的完整生命周期:服务端管理员如何生成、配置和管理 key;客户端用户如何获取和使用 key 访问推理 API。
┌──────────────┐ API Key (Bearer) ┌──────────────┐
│ 客户端用户 │ ─────────────────────────→ │ 网关 │
│ (租户) │ Authorization: Bearer... │ (:8080) │
└──────────────┘ └──────┬───────┘
SHA-256 哈希查找
作用域 + 限流检查
│
┌──────▼───────┐
│ 模型服务器 │
│ (仅环回地址) │
└──────────────┘
┌──────────────────┐ 改 toml 后重启网关 ┌──────────────┐
│ 服务端管理员 │ POST .../servers/... │ 监督器 │
│ (运维人员) │ /restart │ (:8787) │
└──────────────────┘ └──────────────┘
开发默认用 mortredctl init-trust(环境变量 token)。conf/api_keys.toml 是网关进程上的可选多租户鉴权。监督器 没有 key 热加载:改文件后重启 gateway 子进程。scope 只决定该 Bearer 能不能推理,不能管 :8787。
4f8a7b2c9d0e...,64 位十六进制字符串)https://inference.example.com:8080)inference)和限流配额# 保存到受限权限的文件
echo "your-api-key-here" > ~/.mortred-api-key
chmod 600 ~/.mortred-api-key
# 或设置环境变量
export MORTRED_API_KEY="your-api-key-here"
每个发往网关的请求必须携带 Authorization: Bearer <key> 头:
优先走 catalog 路径。遗留 server_uri 仍可用,请求体相同。
IMG=$(base64 -w0 image.jpg)
curl -X POST http://localhost:8080/v1/models/YOLOV8/infer \
-H "Authorization: Bearer $MORTRED_API_KEY" \
-H "Content-Type: application/json" \
-d '{"images":["'"$IMG"'"],"req_id":"my-request-1"}'
提交体与 /infer 相同。DDPM 的 timesteps 等模型参数放在 params 里,不要放在根上。
生成式模型会忽略 images[] 的像素,dummy base64 即可。
# 1. 提交
curl -X POST http://localhost:8080/v1/models/DDPM/jobs \
-H "Authorization: Bearer $MORTRED_API_KEY" \
-H "Content-Type: application/json" \
-d '{"images":["aGVsbG8="],"req_id":"job-1","params":{"timesteps":10}}'
# 返回 HTTP 202: {"job_id": "job_xxx", "state": "pending", "poll_url": "...", "result_url": "..."}
# 2. 轮询
curl http://localhost:8080/v1/models/DDPM/jobs/job_xxx \
-H "Authorization: Bearer $MORTRED_API_KEY"
# 3. 取结果
curl http://localhost:8080/v1/models/DDPM/jobs/job_xxx/result \
-H "Authorization: Bearer $MORTRED_API_KEY"
import requests
import base64
API_KEY = "your-api-key-here"
GATEWAY = "http://localhost:8080"
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
# 编码图片
with open("image.jpg", "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
# 推理
resp = requests.post(f"{GATEWAY}/v1/models/YOLOV8/infer",
headers=headers,
json={"images": [img_b64], "req_id": "demo"})
print(resp.json())
| 响应头 | 说明 |
|---|---|
X-Mortred-Key |
你的 key 名称(如 tenant-a)——确认使用了哪个 key |
X-Request-ID |
回显的请求 ID,用于追踪 |
Retry-After |
429 响应时出现——等待指定秒数后重试 |
| 状态码 | 含义 | 处理方式 |
|---|---|---|
| 401 | Key 无效或缺失 | 检查 key 是否正确且已启用 |
| 429 | 超出限流配额 | 等待 Retry-After 秒后重试 |
| 404 | 未知的模型路径 | 与管理员确认模型 URI |
| 503 | 模型服务器未运行 | 联系管理员 |
export MORTRED_API_KEY="new-key"API Key 定义在 conf/api_keys.toml:
[keys.tenant-a]
# API Key 字符串的 SHA-256 哈希(绝不存储明文)
hash = "a1b2c3d4..."
scope = "inference" # inference | admin | all
rate_limit_qps = 100 # 0 = 不限
enabled = true
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
hash |
string | 必填 | Key 的 SHA-256 十六进制 |
scope |
string | “inference” | “inference”(仅推理)、”admin”(管理)、”all”(两者) |
rate_limit_qps |
int | 0 | 每 key 每秒请求上限(0 = 不限) |
enabled |
bool | true | 禁用 key 而不删除 |
当新客户端(租户)需要访问时:
# 第 1 步:生成随机 key(这个要交给客户端)
openssl rand -hex 32
# 示例输出: 3a7f9b2e8c4d1f6a0b5c3d8e2f7a4b9c6d1e0f3a5b8c2d7e4f1a6b3c8d0e5f
# 第 2 步:计算 SHA-256 哈希(这个写入配置文件)
echo -n "3a7f9b2e8c4d1f6a0b5c3d8e2f7a4b9c6d1e0f3a5b8c2d7e4f1a6b3c8d0e5f" | sha256sum
# 示例输出: 278ea5c810f26733365d39e13857a53bf2d6d1fd8a98f47f668c574cb5417c53
# 第 3 步:将哈希写入 conf/api_keys.toml
# conf/api_keys.toml — 添加新客户端
[keys.new-client]
hash = "278ea5c810f26733365d39e13857a53bf2d6d1fd8a98f47f668c574cb5417c53"
scope = "inference"
rate_limit_qps = 100
enabled = true
# 第 4 步:重启 gateway 子进程以加载 conf/api_keys.toml
curl -X POST -H "Authorization: Bearer $MORTRED_API_TOKEN" \
http://localhost:8787/api/v1/servers/__gateway/restart
# 第 5 步:将 key 字符串交给客户端(不是哈希!)
# 客户端使用: Authorization: Bearer 3a7f9b2e8c4d...
# 你存储的是: hash = "278ea5c8..."
监督器 不 暴露 key 列表或用量计数。计数器在网关进程内,重启后清零。
# conf/api_keys.toml
[keys.suspended-client]
hash = "..."
enabled = false # 重启 gateway 子进程后生效
然后重启 gateway 子进程。
[keys.suspended-client]
hash = "..."
enabled = true
重启 gateway 子进程。
从 conf/api_keys.toml 中删除整个 [keys.name] 段,然后重启 gateway 子进程。
[keys.tenant-a]
hash = "..."
rate_limit_qps = 200 # 原来是 100
重启 gateway 子进程。对新请求立即生效。
文件里同时保留新旧 [keys.*],重启 gateway,切换客户端,再删旧条目并再重启一次。重启期间网关会短暂不可用;一次重启时文件里同时有两个哈希,可避免客户端空窗。
# 限制文件权限(仅服务用户可读)
sudo chown mortred:mortred conf/api_keys.toml
sudo chmod 600 conf/api_keys.toml
# 绝不将生产环境的 key 文件提交到版本控制
echo "conf/api_keys.toml" >> .gitignore
每 key 计数在网关进程内,:8787 不导出。用网关访问日志(X-Mortred-Key)或 Prometheus HTTP 指标。网关子进程重启后计数清零。
| 问题 | 原因 | 解决方式 |
|---|---|---|
| 所有请求 401 | 无 token、空 api_keys.toml、或 Bearer 错误 |
mortredctl init-trust 或写入 [keys.*] 哈希;空/仅注释的 key 文件不算鉴权 |
| 新 key 不生效 | 网关仍在跑旧文件 | 重启 gateway 子进程 |
| Key 可用但返回 429 | 超出限流配额 | 增大 rate_limit_qps 并重启网关 |
| 客户端丢失 key | 只存储了哈希 | 生成新 key,禁用旧 key |
| 网关日志 “failed to parse” | TOML 语法错误 | 修好文件;没有静态 token 时网关拒绝启动 |
| 网关日志 “empty key file is not auth” | 复制了示例但没写哈希 | 补 key 或使用 init-trust token |
# 1. 生成随机 key
openssl rand -hex 32
# 2. 计算配置文件需要的哈希
echo -n "4f8a7b2c..." | sha256sum
# 3. 写入 conf/api_keys.toml
# conf/api_keys.toml
# 实时推理客户端,中等速率
[keys.mobile-app]
hash = "a1b2c3d4e5f6..."
scope = "inference"
rate_limit_qps = 100
enabled = true
# 批处理客户端,高速率
[keys.batch-processor]
hash = "b2c3d4e5f6a7..."
scope = "inference"
rate_limit_qps = 500
enabled = true
# 运维 key(同一推理路径;不能解锁 :8787)
[keys.ops-team]
hash = "c3d4e5f6a7b8..."
scope = "all"
rate_limit_qps = 0
enabled = true
# 暂时停用的客户端
[keys.trial-expired]
hash = "d4e5f6a7b8c9..."
scope = "inference"
rate_limit_qps = 10
enabled = false
网关按顺序检查:
任一通过即授权。响应头 X-Mortred-Key 标识使用了哪个 key。
监督器没有 /api/v1/keys 接口。改完 conf/api_keys.toml 后重启 gateway 子进程:
curl -X POST -H "Authorization: Bearer $MORTRED_API_TOKEN" \
http://localhost:8787/api/v1/servers/__gateway/restart
curl -X POST http://localhost:8080/v1/models/YOLOV8/infer \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"images":["base64..."],"req_id":"demo"}'
openssl rand -hex 32/metricsApiKeyManager::authenticate() 返回 shared_ptr<const ApiKey>:调用方在
读取 key(name/scope/计数器)期间持有所有权,因此并发的 reload() 整体
替换 key 集合也不会使结果悬垂。调用方不得把裸指针保留到 shared_ptr 生命
期之外。ApiKey 上的运行时计数与限流状态是 mutable 的内部同步状态——
const key 仍会计数与限流,但其身份/配置永不变化。
该契约由 test/api_key_manager_unittest.cc 强制执行:压力测试让
authenticate() 与持续 reload 循环并发,并携带 sanitizer ctest label(CI
的 TSAN 门禁)。同一测试对旧的裸指针实现运行会在 ASan 下以
heap-use-after-free 崩溃。