视频云平台的安全实践:Token 刷新机制、数据加密与防重放攻击设计(乐橙 OpenAPI 实战)

视频云平台的安全实践:Token 刷新机制、数据加密与防重放攻击设计(乐橙 OpenAPI 实战)


上线第三个月,运维在日志里抓到同一组 bindDeviceLive 参数被 不同 IP 在 30 秒内各打了一次——签名却都通过了。排查发现:我们网关把 带 token 的 OpenAPI 请求体 原样暴露给了 H5,攻击者抓包重放即可换流。更糟的是,早期 Demo 把 appSecret 写进了小程序配置。那天之后,安全评审多了一条红线:密钥、令牌、播放地址,三层必须分开管。


为什么视频云的安全不能「等上线再补」

监控、巡店、透明工地、在线监考——这类产品的「资产」是 实时画面和历史录像,泄露代价远高于普通 CRUD 接口:隐私、合规、品牌,一次裸链转发就可能上热搜。乐橙开放平台在文档 应用开发开发规范 里反复强调:云云对接,OpenAPI 由 开发者后台 调用,客户端只调自己的 BFF——因为 appId / appSecret 一旦进 App 包,反编译即盗用配额。

对我们这类接乐橙设备的 SaaS,安全可以拆成 四层(均基于现行 OpenAPI,勿引用「旧版本协议、不再维护」栏目):

① 请求鉴权层   time + nonce + sign(防篡改、防简单重放)
② 身份令牌层   accessToken 生命周期(业务权限)
③ 敏感数据层   encryptCode / 设备 encryptMode(验证码与音视频加密)
④ 播放暴露层   直播/录播 URL 短效 + 业务 ticket(防裸链扩散)

很多团队 ① 做了,④ 没做——接口都 200,业主群里仍出现「永久监控链接」。本文按我们踩坑后的 网关实现顺序 写:先 sign 与 token,再加密绑定,最后播放 ticket。

总体架构(推荐)

┌──────────┐     ┌─────────────────────┐     ┌──────────────────┐
│ App/H5   │────▶│ 你的 BFF / 网关      │────▶│ 乐橙 OpenAPI      │
│ 会话 Cookie│◀───│ sign / token 缓存    │◀───│ accessToken 等    │
└──────────┘     │ 权限 / 审计 / 限流   │     └──────────────────┘
                 │ appSecret 仅此处     │
                 └─────────────────────┘
   只拿 playTicket          永不向前端输出 appSecret

1. 签名:每一请求独立的 time + nonce

现行规范:签名原始串 固定顺序拼接后 MD5(UTF-8,32 位小写十六进制):

time:{unix秒},nonce:{32位随机},appSecret:{密钥}
import crypto from 'node:crypto';
import { randomUUID } from 'node:crypto';

function calcSign(time, nonce, appSecret) {
  const raw = `time:${time},nonce:${nonce},appSecret:${appSecret}`;
  return crypto.createHash('md5').update(raw, 'utf8').digest('hex');
}

function buildRequestBody(appId, appSecret, params = {}) {
  const time = Math.floor(Date.now() / 1000);
  const nonce = randomUUID();
  return {
    system: {
      ver: '1.0',
      appId,
      sign: calcSign(time, nonce, appSecret),
      time,
      nonce,
    },
    id: randomUUID(),
    params,
  };
}

防重放要点(平台侧已内置,你要配合)

机制说明
time 窗口与真实时间误差 ≤5 分钟,否则拒绝
nonce 唯一5 分钟内不可重复,重复报 SN1005
每次新 id请求 body 的 id 每次非空唯一

踩坑 A:多实例网关用「内存计数器」生成 nonce → 集群碰撞 SN1005。我们改为 UUID v4,碰撞可忽略。

踩坑 B:服务器 NTP 漂移 → 批量签名失败。监控机器时钟,容器部署尤其要查。

官方标准案例(time=1706511734, 给定 nonce/secret)sign 应为 fd37b62889e4757c58b8f3bf05fb9976——上线前用单测锁死算法,别靠肉眼对。

2. accessToken:缓存 + 主动刷新,别「每点击一次拉一次」

async function getAdminToken(appId, appSecret, cache) {
  const cached = await cache.get('lc:admin:token');
  if (cached) return cached;

  const data = await callOpenApi('accessToken', appId, appSecret, {});
  // data.accessToken, data.expireTime(剩余有效秒数)
  const ttl = Math.max(data.expireTime - 300, 60); // 提前 5 分钟过期
  await cache.set('lc:admin:token', data.accessToken, 'EX', ttl);
  return data.accessToken;
}

文档要点:

  • 管理员 accessToken 有效期约 3 天
  • TK1002 表示 token 失效,清缓存重拉;
  • 使用超过 2 天未满 3 天再次请求时,会发新 token,新旧在各自生命周期内仍可用——刷新时不要假设旧 token 立刻作废,但业务层应统一切新值。

踩坑 C:大促日每个用户打开监控页都调 accessToken → 配额浪费 + 延迟飙升。令牌是 服务级 缓存,不是 用户级

子账号 token:生产建议按门店/租户开子账号,最小权限(如仅 LiveRecordReplayPtz 对应资源 cam:序列号:通道号),泄露面小于管理员 token。

3. 统一网关:业务 API 与 OpenAPI 之间加一道

async function callOpenApi(method, appId, appSecret, params) {
  const body = buildRequestBody(appId, appSecret, params);
  const res = await fetch(`https://openapi.lechange.cn/openapi/${method}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  const json = await res.json();
  const { code, msg, data } = json.result ?? {};
  if (code === 'TK1002') {
    await tokenCache.del('lc:admin:token');
    throw new TokenExpiredError(msg);
  }
  if (code !== '0') throw new PlatformError(code, msg);
  return data;
}

对外只暴露 POST /api/v1/stores/{id}/live,内部再 bindDeviceLive。前端 永远看不到 appSecret、管理员 accessToken、完整 m3u8 长期 URL。

4. 敏感参数加密:encryptCode 绑定设备

bindDevicecode(安全码/密码)可明文传,高安全场景用 encryptCodecodeencryptCode 同时传时 以 code 为准):

encryptCode = Base64(AES256_CBC(code, key[], iv[]))
算法:AES/CBC/PKCS5Padding
key:PBKDF2WithHmacSHA256(deviceId, MD5(appSecret)),迭代 1200 次,256 位
iv:MD5(appSecret)

日志与工单系统 禁止打印明文 code;批量绑定时从 KMS 读 appSecret,内存里算 encryptCode 后立即丢弃明文。

踩坑 D:把 encryptCode 算法写在前端「方便实施」——等于把推导 key 的材料暴露给反编译。加密 只在服务端

5. 播放层:短效 ticket,防裸链

bindDeviceLive / createDeviceRecordHls 返回的 HLS 地址 有时效,文档亦提醒:直播对外公开后他人可直接观看。我们的策略:

async function createPlayTicket(userId, deviceId, channelId) {
  await assertUserCanView(userId, deviceId);
  const live = await issueWallStream(adminToken, deviceId, channelId);
  const ticket = signJwt({
    sub: userId,
    deviceId,
    channelId,
    hls: live.hls,
    exp: Math.floor(Date.now() / 1000) + 300, // 5 分钟
  }, SERVER_PLAY_SECRET);
  auditLog({ userId, deviceId, action: 'live_ticket' });
  return { ticket };
}

前端用 ticket 换播放地址;禁止把 m3u8 写进分享链接、数据库永久字段。会话结束,ticket 作废。

6. 审计、限流与重放防护(业务层)

平台 sign 防的是 对乐橙网关的重放;对你 自家 BFF 仍要补:

// 示例:同一用户 1 秒内只允许 1 次云台指令
async function ptzWithRateLimit(userId, deviceId, op) {
  const key = `ptz:${userId}:${deviceId}`;
  if (await redis.exists(key)) throw new TooManyRequestsError();
  await redis.set(key, '1', 'EX', 1);
  await callOpenApi('controlMovePTZ', appId, appSecret, {
    token: adminToken,
    deviceId,
    channelId: '0',
    operation: String(op),
    duration: '500',
  });
}
控制项建议
直播 ticket5–15 分钟 JWT + 单次绑定 session
云台/绑定用户 + 设备维度限流
审计谁、何时、哪路、何种操作
日志脱敏 appSecret、code、完整 URL

7. 安全验收清单

□ sign 单测与官方案例一致
□ appSecret 仅 KMS/环境变量,仓库无密钥
□ accessToken 服务级缓存 + TK1002 自动刷新
□ 前端/H5/小程序无 OpenAPI 直连
□ bindDevice 生产用 encryptCode 或安全通道传 code
□ 播放 ticket 短效,无永久 m3u8 入库
□ 子账号最小权限(非全局管理员 token 下发客户端)
□ encryptMode≠0 设备已测播放/转存

密钥轮换与事故响应

  • appSecret 轮换:控制台更新后,灰度实例滚动重启;旧 secret 保留 24h 窗口防漏网。
  • 疑似泄露:立即轮换 secret、作废所有 accessToken 缓存、审计近 7 日异常 IP 与设备操作。
  • 直播事故:若 m3u8 已扩散,设备侧重绑或关闭直播计划,而非只改前端。

与 OpenSDK 的边界

文档 标准化 Demo 说明:无后台的 Demo 可能在客户端直调 OpenAPI——仅用于学习。生产必须 BFF + 云云对接;SDK 负责配网、播放、图片解密等端能力,HTTP 敏感操作仍走服务端。

常见错误码(安全相关)

含义处理
SN1005nonce 5 分钟内重复每请求新 UUID
TK1002token 失效清缓存重取 accessToken
sign 相关串顺序/大小写/时钟对照开发规范单测

不要做的「省事」

  • 把 sign 算法 port 到前端「减轻服务器压力」
  • 把管理员 token 塞进 JWT 给移动端
  • 监控墙把 10 路 m3u8 写进 HTML 源码
  • 测试环境与生产共用 appSecret

小结

乐橙视频云接入的安全,可以记成 「四不」

  1. appSecret 不进客户端
  2. accessToken 不每次现拉(缓存 + TK1002 刷新)
  3. sign 不重复使用 nonce(配合 time 窗口防重放)
  4. 播放 URL 不当永久资产(ticket + 审计)

平台在 开发规范 里提供了 sign、令牌、encryptCode 等机制;能不能扛生产,取决于你的 BFF 是否把密钥、令牌、播放暴露三层拆开。若你正在接 listDeviceDetailsByPage、直播、云录像或 PTZ,建议先把 网关 + token 缓存 + 播放 ticket 搭好,再叠业务功能——比事后补安全便宜一个数量级。

如何开始:在 乐橙开放平台 完成开发者注册并创建应用,于控制台获取 AppId 与密钥;在 在线开发文档 查阅开发规范、应用开发、accessToken、绑定设备等章节(请使用现行协议,勿引用旧版本协议栏目)。以上为项目实践记录,生产以最新文档为准。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值