适用版本:ChengOS v0.1.0+ | 最后核对:2026-08-12 | 来源:
crates/cheng-api/src/rest/handlers/auth/mod.rs、crates/cheng-api/src/middleware/channel_auth.rs、crates/cheng-api/src/services/channel_socket_ticket.rs、crates/cheng-api/src/middleware/workspace_context.rs
ChengOS 有四种凭证类型,各司其职。大多数 401 和 403 的困惑都源于用错了类型,因此在开始排查之前值得先通读下表。
| 凭证 | 前缀 | 认证对象 | 作用范围 |
|---|---|---|---|
| JWT 访问令牌 | — | 一个用户 | 整个 API |
| 渠道密钥 | chk_ |
外部渠道适配器 | 仅 /api/v1/channel/* |
| Socket 票据 | chtk_ |
一次 WebSocket 升级 | 单个连接,一次性 |
| MCP 令牌 | — | MCP 客户端 | MCP 服务 |
JWT 访问令牌
常规路径。登录、拿到令牌、在每个请求上带着它。
curl -X POST http://127.0.0.1:3000/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email": "you@example.com", "password": "…"}'
{
"token": "eyJhbGciOi…",
"refreshToken": "eyJhbGciOi…",
"expiresIn": 3600,
"user": { "id": "…", "email": "…", "username": "…" }
}
Authorization: Bearer <token>
Claims
| Claim | 含义 |
|---|---|
sub |
用户 id |
email |
用户邮箱 |
tenant_id |
租户(如适用) |
iat / exp |
签发时间与过期时间 |
token_version |
与 users.token_version 对应 |
token_version 是吊销机制。它在修改密码时递增,从而一次性使该用户此前签发的所有令牌失效。这正是「改密码就等于在所有设备上登出」得以真正生效的原因——没有它,被窃取的令牌会一直有效到过期为止。
生命周期
访问令牌有效期为一小时(expiresIn: 3600)。请用 POST /api/v1/auth/refresh 换取新令牌,而不是重新认证。
POST /api/v1/auth/logout 返回 204,且只作用于客户端——它是在告诉你丢弃令牌,并不会在服务端吊销它。要真正让令牌失效,请修改密码(从而递增 token_version)。
完整认证接口
| 接口 | 用途 |
|---|---|
GET /auth/bootstrap-status |
needsSetup 与 userCount——该安装是否已有用户 |
POST /auth/register |
创建账号,受该部署的注册策略约束 |
POST /auth/login |
获取令牌 |
POST /auth/refresh |
用刷新令牌换取新令牌 |
POST /auth/logout |
客户端丢弃 |
GET /auth/validate |
仅校验令牌 |
GET /auth/me |
当前用户 |
POST /auth/password/forgot · /reset · /change |
密码生命周期 |
GET /setup/status · POST /setup/initialize |
首次初始化 |
工作区授权
认证说明你是谁,X-Workspace-Id 说明你在哪个租户中操作:
X-Workspace-Id: <workspace-uuid>
中间件在认证之后运行,正是为了能读取调用方的 claims 并据此对该请求头做授权。访问一个你无权访问的工作区会得到 403,而绝不会静默回落到某个默认工作区。
渠道密钥(chk_)
渠道密钥用于服务端渠道适配器的认证——WhatsApp、Telegram、Slack、钉钉、企业微信——其中不涉及任何用户会话。
chk_{credential_id_hex_32}_{secret_hex_32}
内嵌的凭证 id 是去掉连字符的 UUID,可实现 O(1) 数据库查找;后半段密钥会与加密的凭证记录进行校验。这些密钥以 provider = "channel_adapter" 的凭证形式存储,密钥本身和工作区元数据加密在 cipher 字段中。
在 /api/v1/workspaces/:workspace_id/channel-keys 管理它们(创建、列出、删除)。
不要在浏览器中使用渠道密钥。 它是长期有效的共享密钥,客户端代码无法保守这个秘密。浏览器使用 JWT——参见网关模型。
Socket 票据(chtk_)
渠道密钥绝不能出现在 URL 中,因为查询字符串会被代理记录,并保存在浏览器和 CLI 历史里。因此渠道客户端通过已认证的 REST 接口把密钥换成票据,并在 WebSocket 升级时一次性出示:
POST /api/v1/channel/socket-ticket # 用 chk_ 认证
# → chtk_…
wss://host/ws/executions?ticket=chtk_…
票据的性质:
- 随机且短命——TTL 为 60 秒。
- 一次性——首次使用即被消费。
- 受作用域约束——携带该密钥的工作区以及可选的 app/channel 作用域,并据此限定这条 socket 上的每一次订阅。
- 前缀刻意不同——用
chtk_而非chk_,因此泄露的票据一眼就能看出不是长期凭证。
未消费的票据在有上限的进程内存中保存,从而保护服务器免受「循环签发票据却不消费」的客户端影响。设置 CHANNEL_SOCKET_TICKET_REDIS_URL 可改为存放在 Redis 中,带 TTL 和原子消费——多实例部署需要这样做,因为在一个实例上签发的内存票据无法在另一个实例上被消费。
MCP 令牌
在 MCP_ENABLED=true 时,应用级 MCP 服务挂载在 /mcp。有两种认证模式:
- 数据库托管的令牌(推荐)——与按工作流动态启动的 MCP 服务采用同一模型。
- 静态
MCP_SERVER_TOKEN——兼容模式。若设置了它,就会被采用。
不设置 MCP_SERVER_TOKEN 即可使用数据库托管路径,它支持逐个签发和吊销令牌,而不是共用一个密钥。
无需认证的接口
这些是刻意开放的:
| 接口 | 原因 |
|---|---|
GET /health · /ready |
负载均衡器与健康检查 |
GET /metrics |
Prometheus 的标准惯例 |
GET /workflows/templates |
模板库需要在登录前加载 |
GET /nodes(读取) |
节点目录并不敏感 |
GET /auth/bootstrap-status |
全新安装必须能表明自己需要初始化 |
| Webhook 路由 | 外部平台会调用它们;它们自带校验机制 |
如果你的部署把运维指标视为敏感信息,请在网络层限制 /metrics。
支撑这一切的密钥
| 变量 | 更改它的后果 |
|---|---|
JWT_SECRET |
所有已签发的会话全部失效 |
CREDENTIAL_MASTER_KEY_1 |
已存储的凭证变得无法读取 |
在对外暴露安装之前,用 openssl rand -hex 32 生成这两个值,并把 CREDENTIAL_MASTER_KEY_1 视为不可恢复的——丢失它就等于丢失所有已存凭证。参见环境变量参考。
演示模式
在 CHENG_DEMO_MODE=true 时,认证仍然可用,但几乎所有写操作都返回 403 DEMO_MODE_RESTRICTED。这是配置问题,不是账号的权限问题。
下一步
- REST API 总览——这些凭证所能访问的接口。
- WebSocket API——票据的使用场景。
- 网关模型——网关应该使用哪种凭证。

暂无评论内容