HTML5 付费视频播放器如何安全使用外部托管文件?
结论
视频可以继续托管在客户自己的服务器上,但该服务器或前置 CDN 必须支持访问鉴权。播放器无法保护公开的视频 URL,也无法阻止已有播放权限的用户通过浏览器查看媒体请求。
可行的设计流程是:
- 用户付款后,后端确认其观看权限。
- 后端签发有效期较短、与具体资源绑定的播放凭证。
- 客户的视频服务器或 CDN 验证凭证。
- 验证通过后,服务器才返回视频文件或流媒体分片。
如果外部服务器只能提供永久公开的 URL,也无法增加鉴权逻辑,那么仅靠 HTML5 播放器无法实现安全的付费访问。
为什么不能依靠播放器隐藏地址
HTML5 <video> 最终必须向服务器请求媒体数据。无论 URL 是直接写在 HTML 中、通过 JavaScript 动态设置,还是经过混淆,用户都能通过浏览器开发者工具、代理软件或网络日志看到请求。
例如,下面的写法没有保护作用:
<video controls src="https://media.example.com/movie.mp4"></video>
以下手段同样不能承担付费鉴权:
- 禁用右键菜单
- 隐藏下载按钮
- 混淆 JavaScript
- 检查
Referer - 限制 CORS
- 将真实 URL 放进前端加密
- 用播放器页面遮挡视频元素
CORS 主要限制其他网页中的 JavaScript 读取跨域响应,并不会阻止用户直接访问媒体 URL。Referer 也可能缺失或被伪造,最多只能用于辅助风控。
推荐的访问流程
典型流程如下:
用户付款
↓
业务后端确认订单和观看权限
↓
签发短时播放令牌或签名 URL
↓
播放器取得播放地址
↓
外部视频服务器/CDN验证签名
↓
返回 MP4、HLS 或 DASH 内容
播放令牌至少应包含:
- 视频资源标识
- 用户或会话标识
- 过期时间
- 随机数或令牌 ID
- 签发方
- 防篡改签名
不要让前端判断支付是否成功。付款回调、订单状态和播放授权都应由可信后端校验。
方案一:短时签名 URL
如果客户服务器支持签名校验,可以在每次播放时生成短时 URL:
https://media.customer.com/videos/123/master.m3u8
?expires=1788000000
&token=...
下面是一个简化的 Node.js 签名示例:
import crypto from "node:crypto";
function createSignedUrl({ baseUrl, resourcePath, userId, expires, secret }) {
const payload = `${resourcePath}\n${userId}\n${expires}`;
const token = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("base64url");
const url = new URL(resourcePath, baseUrl);
url.searchParams.set("uid", userId);
url.searchParams.set("expires", String(expires));
url.searchParams.set("token", token);
return url.toString();
}
外部视频服务器收到请求后,需要重新计算签名:
import crypto from "node:crypto";
function verifyRequest({ resourcePath, userId, expires, token, secret }) {
if (!Number.isInteger(expires) || expires < Math.floor(Date.now() / 1000)) {
return false;
}
const payload = `${resourcePath}\n${userId}\n${expires}`;
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest();
let received;
try {
received = Buffer.from(token, "base64url");
} catch {
return false;
}
return (
received.length === expected.length &&
crypto.timingSafeEqual(received, expected)
);
}
这段代码只展示了核心逻辑。生产环境还要限制签名适用的资源路径、令牌有效期和使用范围,并妥善保管密钥。
签名 URL 在有效期内仍可能被复制,因此有效期应尽量缩短。必要时还可以绑定登录会话、设备或 IP。不过,绑定 IP 可能影响使用移动网络、代理或频繁切换网络的用户。
方案二:签名 Cookie
HLS 和 DASH 通常会请求一个清单文件和大量媒体分片。如果每个分片都要携带查询参数,管理会比较麻烦。对于这种场景,支持签名 Cookie 的 CDN 或媒体服务器通常更合适。
授权成功后,后端可以设置受保护的 Cookie:
Set-Cookie: playback_token=...; Path=/protected/video/123/; Secure; HttpOnly; SameSite=None
播放器加载:
<video
controls
src="https://media.customer.com/protected/video/123/master.m3u8">
</video>
媒体服务器统一验证清单、加密密钥和所有分片请求中的 Cookie。
跨站嵌入时,Cookie 会受到浏览器第三方 Cookie 策略的影响,不能假定所有浏览器都允许这种方式。遇到这类部署结构时,可以改用同站域名、反向代理,或者在 CDN 支持的范围内使用请求头和签名 URL。
方案三:授权服务配合反向代理
如果客户的存储服务器不方便实现鉴权,可以在其前面部署 Nginx、应用网关或 CDN。网关负责验证令牌,源站只接受来自网关的访问。
以 Nginx 的 auth_request 为例:
location /protected-video/ {
auth_request /_verify_playback;
proxy_pass https://private-media-origin;
proxy_set_header Host private-media-origin;
}
location = /_verify_playback {
internal;
proxy_pass https://auth.example.com/api/playback/verify;
proxy_pass_request_body off;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Authorization $http_authorization;
}
授权服务返回 2xx 时允许播放,返回 401 或 403 时拒绝请求。
源站也必须限制公网访问。否则,用户可以绕过网关直接访问源站地址,鉴权便失去了作用。常见做法包括:
- 源站仅向 CDN 或代理服务器开放
- 使用对象存储私有桶
- 通过源站访问控制策略限制来源
- CDN 与源站之间使用独立凭证或双向 TLS
MP4 直传时要支持 Range 请求
浏览器播放 MP4 时通常会发送字节范围请求:
Range: bytes=1048576-
媒体服务器需要正确返回:
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 1048576-2097151/50000000
鉴权不能只检查第一次请求,后续的 Range 请求也必须验证令牌,否则可能留下绕过入口。令牌有效期还要覆盖正常播放过程,或者允许播放器在令牌过期前刷新播放地址。
HLS 或 DASH 的额外要求
使用 HLS 或 DASH 时,不能只保护入口清单。以下资源都要经过鉴权:
- 主清单
- 子清单
- 视频和音频分片
- 字幕文件
- 加密密钥
- 初始化分片
如果只保护 master.m3u8,而 .ts、.m4s 或密钥 URL 仍然公开,用户依旧可以直接下载内容。
播放器可以通过以下方式获取授权地址:
async function startPlayback(videoId) {
const response = await fetch(`/api/videos/${videoId}/playback`, {
method: "POST",
credentials: "include"
});
if (!response.ok) {
throw new Error("Playback authorization failed");
}
const { playbackUrl } = await response.json();
const video = document.querySelector("video");
video.src = playbackUrl;
await video.play();
}
/api/videos/:id/playback 必须由后端检查登录状态、订单状态和观看权限,不能仅根据前端传来的”已付款”字段签发 URL。
外部托管平台需要提供什么
如果要支持第三方客户托管视频,可以规定以下几种接入模式:
- 客户使用支持签名 URL 或签名 Cookie 的 CDN。
- 客户在自己的服务器上安装鉴权中间件。
- 客户允许你的授权服务通过回调接口参与鉴权。
- 客户将私有源站接入你提供的代理或 CDN。
- 客户使用兼容私有对象存储的临时访问地址。
播放器产品除了提供嵌入脚本,还应提供鉴权协议、签名算法、密钥轮换方式、服务器端 SDK 和部署说明。
不要把全局共享密钥放进播放器 JavaScript。浏览器中的密钥最终都能被用户读取,签名操作必须在服务器端完成。
如果要求更强的内容保护
短时签名 URL 解决的是未授权用户直接访问源文件的问题,但无法彻底阻止已授权用户录制或保存播放内容。只要浏览器能够解码并显示视频,内容就有被捕获的可能。
对于高价值内容,可以考虑使用基于 Encrypted Media Extensions 的 DRM,例如:
- Widevine
- FairPlay Streaming
- PlayReady
DRM 通常需要加密打包、许可证服务器,以及支持相应 DRM 的播放器和浏览器环境,部署成本和复杂度都更高。即使使用 DRM,也无法完全防止屏幕录制,但可以明显增加直接下载和离线解密的难度。
实施时的注意事项
- 所有播放页面和媒体请求都应使用 HTTPS。
- 播放凭证应短时有效,并限定到具体视频。
- 签名应使用 HMAC-SHA-256 等成熟算法,避免自行设计加密算法。
- 密钥只能保存在服务器或受控的密钥管理系统中。
- 令牌验证应覆盖清单、分片、字幕、密钥和
Range请求。 - 源站不应保留可绕过 CDN 或代理的公开入口。
- 记录令牌签发、访问拒绝和异常并发等审计信息。
- 可以限制同时播放数量,但要考虑网络重连和多标签页可能造成的误判。
- 不应在访问日志、分析脚本或第三方错误上报中泄露完整的签名 URL。
- 配置缓存时,要避免不同用户的授权响应被错误共享。
播放器负责播放,服务器负责授权。只要视频服务器或 CDN 能根据付款后签发的凭证拒绝未授权请求,外部托管就可以实现。如果托管端始终提供公开文件,就没有可靠的方法只靠 HTML5 播放器补救。
备注:内容仅供参考。
