Zoom Cobrowse SDK JWT 认证实战:在 knowledge-work-plugins 中构建安全的双角色令牌服务
【免费下载链接】knowledge-work-pluginsOpen source repository of plugins primarily intended for knowledge workers to use in Claude Cowork项目地址: https://gitcode.com/GitHub_Trending/kn/knowledge-work-plugins
Cobrowse(协同浏览)会话中,客户与客服代理都必须持有 JWT 才能鉴权接入 Zoom 的实时会话。本指南以 concepts/jwt-authentication.md 为核心骨架,完整讲解如何在服务端用 SDK Key 与 SDK Secret 生成 Cobrowse JWT、如何为role_type=1(客户)与role_type=2(代理)签发不同令牌,以及如何搭建生产可用的令牌服务端点。读完本文,你将能独立实现一套安全合规的 JWT 签发与校验流程,并理解令牌中每个 claim 的确切含义与约束。
1. 为什么 JWT 必须由服务端签发
Zoom Cobrowse 会话的鉴权基于 JWT(JSON Web Token),其签名算法为 HS256,签名密钥是你在 Zoom 开发者后台获取的SDK Secret。这份凭据的保密等级直接决定整套认证体系是否安全。
在 SKILL.md 的凭据总览中,Cobrowse SDK 一共涉及 4 个凭据,它们的暴露范围完全不同:
| 凭据 | 类型 | 用途 | 可否暴露在客户端 |
|---|---|---|---|
| SDK Key | 公开 | 出现在 CDN 加载 URL 与 JWT 的app_keyclaim 中 | ✅ 可以(本就会被浏览器看到) |
| SDK Secret | 私密 | 用于 JWT 签名 | ❌ 绝对不可以 |
| API Key | 私密 | REST API 鉴权(可选) | ❌ 不可以 |
| API Secret | 私密 | REST API 鉴权(可选) | ❌ 不可以 |
核心结论有两点:
- SDK Key 是公开的,它会被内嵌进客户页面的 CDN 加载地址,例如
https://us01-zcb.zoom.us/static/resource/sdk/${ZOOM_SDK_KEY}/js/2.13.2,因此可以被前端看到; - SDK Secret 必须永远留在服务端,一旦泄露,任何人都能以任意
role_type伪造令牌、冒充客户或代理进入会话。
因此,jwt-authentication.md 将“用 SDK Key 与 SDK Secret 在服务端生成 Cobrowse JWT”作为首要指导原则。对应地,get-started.md 在配置令牌服务时明确强调:JWT 签名必须在服务端完成,以保护 SDK Secret。
错误与正确的做法对比(摘自 SKILL.md):
// ❌ 错误:SDK Secret 暴露在前端代码中 const jwt = signJWT(payload, 'YOUR_SDK_SECRET'); // 安全风险! // ✅ 正确:Secret 留在服务端,前端只向自己的后端请求令牌 const response = await fetch('/api/token', { method: 'POST', body: JSON.stringify({ role: 1, userId, userName }) }); const { token } = await response.json();2. JWT 结构:Header 与 Payload 全字段说明
所有 Cobrowse JWT 使用相同的 Header:
{ "alg": "HS256", "typ": "JWT" }签名方式在 get-started.md 中有明确说明——使用 SDK Secret(不是API Secret)对base64UrlEncode(header) + '.' + base64UrlEncode(payload)做 HMAC-SHA256:
HMACSHA256( base64UrlEncode(header) + '.' + base64UrlEncode(payload), ZOOM_SDK_SECRET );2.1 Payload 字段表
Payload 中的 claim 及约束如下(综合 authorization-official.md 与 get-started.md 两张权威表):
| Claim | 必填 | 类型 | 说明 |
|---|---|---|---|
app_key | ✅ | string | 你的 Zoom SDK Key(不是API Key),用于客户与代理的 JWT |
role_type | ✅ | number | 用户角色:1= 客户(customer),2= 代理(agent) |
iat | ✅ | number | 令牌签发时间戳(epoch 秒) |
exp | ✅ | number | 令牌过期时间戳(epoch 秒)。最小 30 分钟,最大 48 小时 |
user_id | ✅ | string | 唯一可识别的用户 ID |
user_name | ✅ | string | 用户名,最长 80 个字符 |
enable_byop | 可选 | number | 启用 BYOP(Bring Your Own PIN,自带 PIN):1= 启用,0或不传 = 关闭 |
其中exp的时间窗口约束(最小 30 分钟、最大 48 小时)与user_name的 80 字符上限,是实现令牌服务时必须校验的硬性边界。
2.2 严格 claim 命名(重点)
authorization-official.md 特别强调:Cobrowse 令牌校验是严格的,必须使用以下确切 claim 名称:
user_id(不是user_identity)app_keyrole_typeuser_nameiatexp
除非 Zoom 官方文档明确支持,否则不要添加任何未被认可的扩展 claim。如果收到Invalid token(错误码124),应首先核对 claim 名称是否正确。
3. 双角色模型:为客户与代理分别签发令牌
Cobrowse 会话中存在两个完全不同的角色(详见 concepts/two-roles-pattern.md):
| 角色 | role_type值 | 含义 |
|---|---|---|
| 客户 Customer | 1 | 主动分享自己浏览器画面的一方 |
| 代理 Agent | 2 | 查看会话并提供支持的服务人员 |
jwt-authentication.md 提出的三条安全准则之一就是“为客户和代理角色生成不同的令牌”——即使同一用户在不同时间点切换身份,也不应复用令牌。两角色使用同一套 JWT 认证模式,但负载内容因角色而异。
3.1 客户令牌示例(role_type=1)
const customerPayload = { user_id: "customer_123", app_key: "YOUR_SDK_KEY", role_type: 1, user_name: "John Customer", iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600 }; const token = jwt.sign(customerPayload, SDK_SECRET, { algorithm: 'HS256' });3.2 代理令牌示例(role_type=2)
const agentPayload = { user_id: "agent_456", app_key: "YOUR_SDK_KEY", role_type: 2, user_name: "Support Agent", iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600 }; const token = jwt.sign(agentPayload, SDK_SECRET, { algorithm: 'HS256' });两份示例均来自 references/authorization-official.md,过期时间统一为签发后 1 小时(3600 秒),符合“短时令牌”的要求。
4. 搭建令牌服务:从零到可上线的端点
4.1 可复用的签发函数
将签发逻辑封装为单一函数,references/get-started-official.md 给出了 Node.js 参考实现:
const jwt = require('jsonwebtoken'); function generateCobrowseToken(userId, userName, roleType) { const iat = Math.floor(Date.now() / 1000); const exp = iat + 3600; // 1 小时 const payload = { user_id: userId, app_key: SDK_KEY, role_type: roleType, // 1 = customer, 2 = agent user_name: userName, iat: iat, exp: exp }; return jwt.sign(payload, SDK_SECRET, { algorithm: 'HS256' }); }注意SDK_KEY与SDK_SECRET应来自服务端环境变量,不要硬编码进代码。
4.2 令牌请求 / 响应契约
get-started.md 定义了令牌服务的统一 HTTP 契约——前端(客户页与代理页)通过 POST 请求携带角色信息换取 JWT:
// POST https://YOUR_TOKEN_SERVICE_BASE_URL { "role": 1, // 1 = customer, 2 = agent "userId": "user123", "userName": "John Doe" } // 响应 { "token": "eyJhbGciOiJIUzI1NiIs..." }4.3 官方参考实现与本仓库的端点拆分建议
官方提供了可克隆的令牌端点样例项目(zoom/cobrowsesdk-auth-endpoint-sample),其典型启动流程是:克隆 →npm install→ 写入.env(含ZOOM_SDK_KEY、ZOOM_SDK_SECRET、PORT)→npm start,服务将运行在你为令牌服务配置的 base URL 上。
在此基础上,concepts/two-roles-pattern.md 给出了更贴合业务的后端端点拆分建议,将“签发令牌”与“会话管理”职责分离:
POST /api/customer/start→ 创建会话记录 + 签发客户令牌 + 生成 PINPOST /api/agent/connect→ 校验 PIN + 签发代理令牌POST /api/session/revoke→ 结束会话GET /api/session/list→ 运营可见性(查询会话状态)
其中代理令牌只有在 PIN 校验通过后才签发,这从流程上保证了代理无法绕过 PIN 直接获取入场凭证。
5. 安全准则:三条不可妥协的红线
jwt-authentication.md 在开篇即列出三条安全准则,结合 references/authorization-official.md 的 Security 章节,可以归纳为以下实现清单:
- SDK Secret 绝不暴露在客户端
- 前端代码、CDN 资源、浏览器 DevTools 中都不能出现 SDK Secret;
- 只有 SDK Key 允许出现在前端(CDN URL 与
app_keyclaim)。
- 签发短时令牌(short-lived tokens)
exp的合法区间为 30 分钟到 48 小时,官方示例普遍采用 1 小时;- 短生命周期可最小化令牌泄露后的被利用窗口。
- 不同角色使用不同令牌
- 客户令牌(
role_type=1)与代理令牌(role_type=2)分开生成,永不混用; - 令牌签发只发生在服务端,客户端一律通过令牌服务接口获取。
- 客户令牌(
此外,references/authorization-official.md 建议为exp设置合理的过期时间,避免因过长有效期放大泄密风险。
6. 令牌在完整会话流程中的位置
JWT 不是孤立存在的,它嵌入在 concepts/session-lifecycle.md 描述的完整流程中:
- 在客户页与代理页分别初始化 SDK;
- 服务端按角色生成 JWT 令牌;
- 客户发起会话并收到 PIN;
- 代理使用 PIN 加入会话;
- 会话事件跟踪 connected / disconnected / end 状态。
典型的客户发起型会话(get-started.md 的“客户 JWT 流程”)中,令牌的具体走向为:
- 客户侧:前端 POST
{role: 1, userId, userName}到令牌服务 → 拿到客户 JWT →session.start({ sdkToken: token })启动会话; - 代理侧:前端 POST
{role: 2, userId, userName}到令牌服务 → 拿到代理 JWT → 将令牌拼入 Zoom 托管坐席台的 iframe 地址https://us01-zcb.zoom.us/sdkapi/zcb/frame-templates/desk?access_token=${token},代理再输入客户 PIN 完成连接。
6.1 一个容易踩的坑:PIN 的唯一来源
在令牌与会话管理对接时,concepts/two-roles-pattern.md 强调:交付给代理使用的 PIN,必须是客户 SDK 事件session.on("pincode_updated", ...)发出的值,而不是后端预启动占位记录中的临时 PIN。UI 中只展示一个明确标注的 PIN(例如 “Support PIN”),并在代理链接中复用同一个值。如果忽略此规则,代理坐席台常会以Pincode is not found(错误码30308)失败。
6.2 需要创建的服务端对象
按 concepts/two-roles-pattern.md,真实实现中通常按顺序创建以下对象:
- 客户会话记录(服务端):包含
session_id、生成的 PIN、状态(active/revoked)、过期时间戳; - 客户令牌(
role_type=1):供客户浏览器 SDK 启动/分享会话; - 代理令牌(
role_type=2):在 PIN 校验通过后签发,用于加载代理坐席 iframe 或自定义代理 UI。
7. 常见故障与排查建议
结合 references/authorization-official.md 与 get-started.md 的测试与排障章节,JWT 相关的常见问题可按以下顺序排查:
| 症状 | 优先检查项 |
|---|---|
Invalid token(错误码124) | 先核对 claim 名称:user_id(而非user_identity)、app_key、role_type、user_name、iat、exp是否拼写完全一致;再检查是否添加了未被官方文档支持的扩展 claim |
| 令牌被服务端拒绝 | 确认签名使用的是 SDK Secret 而非 API Secret;确认app_key是 SDK Key 而非 API Key |
| 令牌频繁失效 | 检查exp是否落在 30 分钟 ~ 48 小时的合法区间内,时钟是否与 NTP 对齐 |
| 代理无法连接 | 确认使用的是pincode_updated事件中的真实 PIN,且会话仍处于 active 状态;确认页面使用 HTTPS(仅 loopback/本地开发允许 HTTP) |
8. 进一步阅读
本文对应的仓库文档体系如下,可继续深入:
- 概念文档:concepts/jwt-authentication.md、concepts/two-roles-pattern.md、concepts/session-lifecycle.md
- 权威参考:references/authorization-official.md、references/get-started-official.md
- 完整实操:get-started.md(含从凭据获取到首个会话的完整步骤)
- 技能入口与凭据总览:SKILL.md
掌握本节内容后,你便拥有了一套安全、可复用的 Zoom Cobrowse SDK JWT 认证实现:服务端独享 SDK Secret、按角色签发短时令牌、严格遵循官方 claim 命名,并与 PIN 驱动的会话生命周期无缝衔接。
【免费下载链接】knowledge-work-pluginsOpen source repository of plugins primarily intended for knowledge workers to use in Claude Cowork项目地址: https://gitcode.com/GitHub_Trending/kn/knowledge-work-plugins
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考