Zoom Cobrowse SDK JWT 认证实战:在 knowledge-work-plugins 中构建安全的双角色令牌服务
2026/9/13 13:34:01 网站建设 项目流程

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 鉴权(可选)❌ 不可以

核心结论有两点:

  1. SDK Key 是公开的,它会被内嵌进客户页面的 CDN 加载地址,例如https://us01-zcb.zoom.us/static/resource/sdk/${ZOOM_SDK_KEY}/js/2.13.2,因此可以被前端看到;
  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_keystring你的 Zoom SDK Key(不是API Key),用于客户与代理的 JWT
role_typenumber用户角色:1= 客户(customer),2= 代理(agent)
iatnumber令牌签发时间戳(epoch 秒)
expnumber令牌过期时间戳(epoch 秒)。最小 30 分钟,最大 48 小时
user_idstring唯一可识别的用户 ID
user_namestring用户名,最长 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_key
  • role_type
  • user_name
  • iat
  • exp

除非 Zoom 官方文档明确支持,否则不要添加任何未被认可的扩展 claim。如果收到Invalid token(错误码124),应首先核对 claim 名称是否正确。

3. 双角色模型:为客户与代理分别签发令牌

Cobrowse 会话中存在两个完全不同的角色(详见 concepts/two-roles-pattern.md):

角色role_type含义
客户 Customer1主动分享自己浏览器画面的一方
代理 Agent2查看会话并提供支持的服务人员

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_KEYSDK_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_KEYZOOM_SDK_SECRETPORT)→npm start,服务将运行在你为令牌服务配置的 base URL 上。

在此基础上,concepts/two-roles-pattern.md 给出了更贴合业务的后端端点拆分建议,将“签发令牌”与“会话管理”职责分离:

  • POST /api/customer/start→ 创建会话记录 + 签发客户令牌 + 生成 PIN
  • POST /api/agent/connect→ 校验 PIN + 签发代理令牌
  • POST /api/session/revoke→ 结束会话
  • GET /api/session/list→ 运营可见性(查询会话状态)

其中代理令牌只有在 PIN 校验通过后才签发,这从流程上保证了代理无法绕过 PIN 直接获取入场凭证。

5. 安全准则:三条不可妥协的红线

jwt-authentication.md 在开篇即列出三条安全准则,结合 references/authorization-official.md 的 Security 章节,可以归纳为以下实现清单:

  1. SDK Secret 绝不暴露在客户端
    • 前端代码、CDN 资源、浏览器 DevTools 中都不能出现 SDK Secret;
    • 只有 SDK Key 允许出现在前端(CDN URL 与app_keyclaim)。
  2. 签发短时令牌(short-lived tokens)
    • exp的合法区间为 30 分钟到 48 小时,官方示例普遍采用 1 小时;
    • 短生命周期可最小化令牌泄露后的被利用窗口。
  3. 不同角色使用不同令牌
    • 客户令牌(role_type=1)与代理令牌(role_type=2)分开生成,永不混用;
    • 令牌签发只发生在服务端,客户端一律通过令牌服务接口获取。

此外,references/authorization-official.md 建议为exp设置合理的过期时间,避免因过长有效期放大泄密风险。

6. 令牌在完整会话流程中的位置

JWT 不是孤立存在的,它嵌入在 concepts/session-lifecycle.md 描述的完整流程中:

  1. 在客户页与代理页分别初始化 SDK;
  2. 服务端按角色生成 JWT 令牌;
  3. 客户发起会话并收到 PIN;
  4. 代理使用 PIN 加入会话;
  5. 会话事件跟踪 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,真实实现中通常按顺序创建以下对象:

  1. 客户会话记录(服务端):包含session_id、生成的 PIN、状态(active/revoked)、过期时间戳;
  2. 客户令牌(role_type=1:供客户浏览器 SDK 启动/分享会话;
  3. 代理令牌(role_type=2:在 PIN 校验通过后签发,用于加载代理坐席 iframe 或自定义代理 UI。

7. 常见故障与排查建议

结合 references/authorization-official.md 与 get-started.md 的测试与排障章节,JWT 相关的常见问题可按以下顺序排查:

症状优先检查项
Invalid token(错误码124先核对 claim 名称:user_id(而非user_identity)、app_keyrole_typeuser_nameiatexp是否拼写完全一致;再检查是否添加了未被官方文档支持的扩展 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询