视频文件往往体积大、消耗带宽多,一旦被其他站点直接嵌入播放地址,服务器的流量会迅速被消耗殆尽,严重的甚至导致正常用户无法访问。Referer防盗链是最常见的方案,但Referer可以被伪造,且部分客户端干脆不发送Referer,可靠性有限。相比之下,签名验证防盗链给每个视频URL附加一段动态计算出来的校验参数,链接过期即失效,即使被盗取也是短命的,安全性明显更高。本文以Apache为例,讲解如何在服务器端实现视频防盗链签名验证。

一、为什么Referer防盗链不够用
Apache自带的防盗链手段主要基于mod_rewrite对请求头中的Referer字段做判断,属于明文规则匹配。它的原理是检查访问来源是否在白名单内,不在则拒绝或重定向。典型配置如下:
<Directory /var/www/video>
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?ipipp\.com [NC]
RewriteCond %{HTTP_REFERER} !^$
RewriteRule \.(mp4|flv|m3u8|ts)$ - [F,L]
</Directory>这套配置对普通图片防盗链够用,但用在视频场景问题不少。第一,Referer头完全可以在客户端伪造,写个脚本带上你的域名Referer就能绕过;第二,一些播放器、App内嵌WebView默认不发送Referer,规则里放行空Referer等于开了后门,不放行又误伤正常用户;第三,Referer规则无法控制链接的有效期,地址一旦泄露就可以被无限期使用。对于有价值的视频内容,必须引入带时效性的签名机制。
签名验证的核心思路是:服务端用密钥对资源路径和过期时间做单向运算,生成一个签名拼在URL后面。Apache收到请求后用同样的算法重新计算,比对结果并检查时间是否过期。签名正确且未过期才放行,否则返回403。由于密钥只存在于服务器端,攻击者拿不到密钥就无法伪造有效签名。
二、Apache模块实现签名校验
最直接的实现方式是使用专门的Apache模块,例如mod_auth_token,它是专为防盗链签名设计的第三方模块。其签名的URL格式形如/protected/hash/timestamp/path,其中hash由密钥、IP、过期时间戳和文件路径共同计算得出。配置示例:
LoadModule auth_token_module modules/mod_auth_token.so <Location /video/> AuthTokenSecret "MySecretKey2024" AuthTokenPrefix /video/ AuthTokenTimeout 3600 AuthTokenLimitByIp off </Location>
上面配置中,AuthTokenSecret是签名密钥,必须和生成链接的程序端保持一致;AuthTokenTimeout设置链接有效期,单位为秒,这里设置为3600即一小时后失效;AuthTokenLimitByIp如果开启,签名会和请求者IP绑定,换一个IP访问就失效,安全性更高但动态IP用户可能受影响,一般对视频站建议关闭。
对应的PHP生成签名链接的代码如下,注意算法要与模块内部一致:
<?php
$secret = 'MySecretKey2024';
$path = '/video/demo.mp4'; // 受保护目录下的相对路径
$expires = time() + 3600; // 一小时后过期
// mod_auth_token 的哈希算法:MD5(secret + path + 时间戳十六进制)
$timestamp = sprintf("%08x", $expires);
$hash = md5($secret . $path . '-' . $timestamp, true);
$hash = str_replace(array('+','/','='), array('-','_',''), base64_encode($hash));
$signedUrl = 'https://www.ipipp.com/video/' . $hash . '/' . $timestamp . '/demo.mp4';
echo $signedUrl;这个方案的关键在于密钥保管。密钥泄露等于整个防护体系失效,因此密钥不要写死在代码里提交到版本库,建议放在环境变量或独立的配置文件中,并定期轮换。签名有效期也要平衡体验与安全:太短用户看到一半链接就失效,太长又给盗链者可乘之机,视频播放场景一般控制在1到6小时比较合理。
三、用mod_rewrite配合后端接口实现轻量方案
如果服务器不方便安装第三方模块,可以用mod_rewrite加CGI脚本的组合实现类似效果。思路是Rewrite把带签名参数的请求交给一个校验脚本,脚本验证通过后内部重定向到真实文件。配置如下:
RewriteEngine On
RewriteCond %{REQUEST_URI} \.(mp4|m3u8|ts)$
RewriteCond %{QUERY_STRING} sign=([a-f0-9]+)&expires=(\d+)
RewriteRule ^/video/(.*)$ /cgi-bin/verify.php?file=%1&sign=%2&expires=%3 [PT,L]verify.php里的校验逻辑很直观:先检查过期时间是否大于当前时间,再用密钥拼接文件路径和过期时间做MD5,与URL中的sign比对。任何一步不通过就直接返回403状态码。这种方式的好处是不依赖额外模块,移植性好,缺点是每个视频请求都要经过PHP处理,高并发下性能开销比C写的Apache模块大,需要根据站点流量权衡。
还有一个容易被忽略的细节:视频播放器请求m3u8切片时,签名链接通常只加在m3u8主文件上,切片ts文件的地址是写在m3u8内容里的。如果切片没有保护,盗链者可以直接拿走切片地址。解决办法是对m3u8做动态改写,播放器请求m3u8时由后端实时生成带各自签名的切片URL,或者干脆对整个目录统一启用签名校验。
四、与CDN配合的注意事项
很多视频站会用CDN加速,此时签名验证应该发生在CDN边缘节点而不是回源源站的Apache。主流CDN都提供URL鉴权功能,算法与上面讲的原理一致,都是密钥加时间戳的哈希运算。需要做的是在源站Apache上也保留签名校验作为兜底,防止有人绕过CDN直接访问源站IP。同时通过防火墙或Apache的访问控制,限制视频端口只允许CDN回源网段访问:
<Directory /var/www/video>
Require ip 1.2.3.4 5.6.7.8 # 替换为CDN回源网段
</Directory>另外要处理好时钟同步问题。签名依赖时间戳,如果生成链接的应用服务器与校验的服务器时钟偏差过大,会出现签名提前失效或迟迟不过期的异常,所有相关服务器都应开启NTP同步。密钥轮换时也要采用新旧密钥并存的过渡策略,先在两端同时支持新密钥,观察日志确认旧签名流量归零后再移除旧密钥,避免一刀切导致正在播放的用户瞬间全部断流。把这些细节处理好,一套基于Apache的视频防盗链签名验证体系就基本牢靠了。