0019-browser-tokens-and-sdk-distribution.md 2.9 KB

ADR-0019 浏览器令牌、CORS 与 SDK 包分发

  • 状态:Accepted · 日期:2026-10-23 · 补充 ADR-0015

背景

业务平台(Vue + TS + UnoCSS)需要在浏览器里直接对话:流式事件、审批、上传文件。原设计在前端直接使用 API Key,这样任何能打开平台页面的人都能从开发者工具中拿到 Key 并长期使用。另外,@agent-server/client / chat-ui 只是仓库内的 TS 源码,平台项目无法直接安装。

决策

  • 浏览器令牌:平台后端用 API Key 调 POST /v1/browser-tokens 换取短期令牌 bt_…,交给浏览器使用。
    • 默认 1 小时,最长 12 小时
    • 令牌无状态:HMAC-SHA256 签名,载荷含签发它的 Key id、过期时间和随机数;密钥随机生成,存在数据库 settings 中,服务重启后仍然有效
    • 每次使用都检查 Key 仍然启用,停用或删除 Key 即作废它签发的全部令牌
    • 浏览器令牌不能再签发令牌;会话、用量都归属于签发它的 Key
    • 平台后端应先校验自己的登录态,只为已登录用户签发
  • 客户端:
    • AgentClient 新增 getToken
    • browserTokenProvider 负责缓存令牌、过期前 60 秒换新、合并并发请求;遇到 401 时刷新令牌并重试一次
    • 请求使用 credentials: 'same-origin':后台同源时继续使用 Cookie,跨域时不带 Cookie
  • CORS:
    • 只对 /v1 开放给后台"设置 › 安全 › CORS 来源"中逐个列出的地址(https://host[:port])
    • 不返回 Access-Control-Allow-Credentials
    • /admin/v1、/mcp 不开放
    • SSE 响应在接管连接后同样带上 CORS 头
  • SDK 分发:scripts/build-sdk.mjs 构建三个包,部署时发布到服务的 /sdk/(公开,不含密钥),平台用 URL 安装。
    • 三个包:@agent-server/shared(类型与常量)、@agent-server/client、@agent-server/chat-ui(ESM + 类型声明 + 编译好的 style.css)
    • 包之间是对等依赖(peerDependencies),平台同时安装三个包,pnpm 按包名关联,不访问 npm 仓库
    • 文件名带版本号,内容不可变(长期缓存);旧版本保留,平台可以锁定版本
  • 样式隔离:chat-ui 使用 UnoCSS vue-scoped 模式,生成的原子类写入各组件的 scoped 样式,不与平台自己的 UnoCSS / Tailwind 类冲突。
    • style.css 只包含 --app-* 设计变量,以及作用于 .agent-chat 内部的基础样式,不改动宿主页面的 html / body
    • 平台可以覆盖 --app-* 变量调整配色;<html data-theme="light|dark"> 可以指定亮/暗

后果

  • API Key 只存在于平台后端,泄露面缩小到短期令牌
  • 没有后端的纯前端平台需要加一个最小的令牌接口(examples/platform-demo/server.mjs 约 30 行)
  • 升级 SDK:修改 packages/*/package.json 的版本号后部署,平台改安装 URL 中的版本号