Skip to content

配方 · 签名 URL 与过期处理

参考实现:examples/team-video-vue/composables/useVideoSource.ts

需求:视频要防盗链,URL 带签名且会过期。过期之后怎么办。


第一件事:SDK 不会替你续签

这是 ADR-022 的明确决定。SDK 只接受已经签好的 URL,不知道你的签名算法, 也不会在过期时去换一个新的。

ts
export function videoToSource(video: VideoDetail): MediaSource {
  return {
    url: video.hlsUrl,        // 假设 hlsUrl 已经是签名 URL
    type: 'hls',
    poster: video.poster,
    metadata: { title: video.title, duration: video.duration },
  } as MediaSource
}

签名从哪来?你的后端接口返回的时候就带上。前端不参与签名计算 —— 签名密钥不能进浏览器。


为什么不用 header 鉴权

因为做不到

<video> 和 HLS 分片请求由播放内核发起,SDK 不提供任何接管点 —— source.onBeforeRequest 曾经看起来像那个接管点,但它是个零消费点的幽灵字段, 已由 ADR-057 废弃。

而且这不是"暂时没做":函数搬不过 postMessage,五种接入方式里的三种 iframe 方案 天然拿不到它;iOS < 17.1 回落原生 HLS 时分片请求更是完全在系统层,JS 根本够不着。

所以 SDK 曾经有过的 source.headers / source.getHeaders移除了 (ADR-022)—— 留着只会让人以为能用。签名 URL 是唯一在所有平台都成立的方案。


过期时会发生什么

拿到 E_AUTH_EXPIRED,category: 'auth',retryable: false

ts
function handleError(payload: PlayerError) {
  switch (payload.code) {
    case 'E_AUTH_EXPIRED':
      message = t('error.authExpired')
      break
    // ...
  }
  showToast({ message, type: 'fail' })
}

retryable: false 是有意的

原样重试必然再次 401/403 —— URL 还是那个过期的 URL。

自动重试在这里不但没用,还会连着打几次鉴权失败的请求。


正确的续期做法

拿到 E_AUTH_EXPIRED重新问后端要一个新签名 → 改 source prop:

ts
async function handleError(payload: PlayerError) {
  if (payload.code !== 'E_AUTH_EXPIRED') return

  const fresh = await api.getVideoDetail(videoId)   // 你的接口,带新签名
  currentSource.value = videoToSource(fresh)        // 改 prop = 换源
}

source 就够了,不用调任何方法(load 不在句柄上)。

加个次数上限

如果后端一直返回过期签名(时钟不同步、配置错误),上面这段会变成死循环。

ts
let refreshed = 0
async function handleError(payload) {
  if (payload.code !== 'E_AUTH_EXPIRED' || refreshed >= 2) return
  refreshed += 1
  // ...
}

参考实现里没有这段

AuthGatedVideoPlayer.vue 只把 E_AUTH_EXPIRED 弹了个 toast,没有续签逻辑

那份代码演示的是清晰度鉴权,不是签名续期。要做续期得自己加 —— 这里写的就是加法。


更好的办法:别让它过期

被动续期总会让用户看到一次卡顿。主动续期体验好得多:

ts
// 后端返回签名有效期
const { url, expiresAt } = await api.getPlayUrl(videoId)

// 提前 2 分钟换新的
const refreshIn = expiresAt - Date.now() - 120_000
setTimeout(async () => {
  const fresh = await api.getPlayUrl(videoId)
  currentSource.value = { ...currentSource.value, url: fresh.url }
}, refreshIn)

签名有效期建议大于视频时长,这样点播场景根本不会遇到过期。 只有长直播才真的需要中途换签名。


403 和签名过期分不开

契约里 auth 这一类只有 E_AUTH_EXPIRED 一个码

原因很实在:HTTP 层面 403 就是 403,拿不到「因为签名过期」还是「因为你没权限」。 硬分成两个码只会让消费方按一个猜出来的区别写分支。

所以统一按「过期」处理 —— 重新要一次签名,要不到就是真没权限。


别在 URL 里放长期凭证

签名 URL 会出现在:浏览器历史、Referer、CDN 日志、用户分享出去的链接里。

所以签名应该短时效 + 绑定资源,而不是把一个长期 token 塞进 query。 前者泄露了几分钟后就失效,后者泄露就是全量泄露。


相关