API 认证:JWT、工作区与渠道密钥

适用版本:ChengOS v0.1.0+ | 最后核对:2026-08-12 | 来源:crates/cheng-api/src/rest/handlers/auth/mod.rscrates/cheng-api/src/middleware/channel_auth.rscrates/cheng-api/src/services/channel_socket_ticket.rscrates/cheng-api/src/middleware/workspace_context.rs

ChengOS 有四种凭证类型,各司其职。大多数 401403 的困惑都源于用错了类型,因此在开始排查之前值得先通读下表。

凭证 前缀 认证对象 作用范围
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 needsSetupuserCount——该安装是否已有用户
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。这是配置问题,不是账号的权限问题。

下一步

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容