ThinkPHP8视频防盗需五步:1. 视频移出Web根目录并隔离应用;2. 生成会话绑定的动态Token;3. 实现支持Range请求的PHP流式解密代理;4. HLS分片AES-128加密与动态m3u8生成;5. 关闭调试模式并加固路由。

ThinkPHP8 防盗版机制与视频流加密技术【安全】需解决视频被直接下载、m3u8被抓取、Token被复用、HLS密钥泄露等实际攻击路径。单纯依赖Referer白名单或静态URL已完全失效,必须组合会话绑定、动态密钥分发、AES-128分片加密与PHP流式代理输出。
第一步:将视频文件移出Web根目录并配置TP8应用级隔离
视频原始文件绝不能放在public/或web/下,否则任何知道路径的人都能直链下载。执行以下操作:
删除app/controller目录→运行php think build video创建独立app/video/应用→把所有加密视频存入/data/videos/(非Web可访问路径)→在app/video/config/app.php中设置'url_domain_deploy' => true。
这一步是后续所有安全机制的前提。若视频仍在public目录,后面所有加密逻辑都会被绕过。
立即学习“PHP免费学习笔记(深入)”;
第二步:生成带会话绑定的动态播放Token
Token必须绑定用户登录态+时间戳+HMAC签名,且不可跨设备复用。
方法一:在app/video/controller/Video.php中写入:
$token = hash_hmac('sha256', $uid . $videoId . time(), session('key') ?: 'fallback_secret');
$expires = time() + 300;
return redirect('/video/play?vid=' . $videoId . '&t=' . base64_encode($token) . '&e=' . $expires);
注意:【session('key')必须在用户登录成功后由TP8自动注入,不可硬编码】。若使用Redis存储session,请确认config/cache.php中'default' => 'redis'已启用。
方法二:配合OSS签名URL(适用于CDN分发场景)——调用think-oss扩展生成含Expires和Signature参数的临时链接,有效期严格控制在5分钟内。
第三步:实现支持Range请求的PHP流式解密代理
客户端拖动进度条时,浏览器会发送Range: bytes=1024-2047请求,服务器必须返回206 Partial Content并正确设置Content-Range头,否则播放器卡死。
第一步:在app/video/controller/Play.php中编写核心逻辑:
检查$_GET['t']有效性 → 解析Range头 → 打开/data/videos/{$vid}.enc → 定位文件偏移 → 每次读取8192字节 → 使用openssl_decrypt($chunk, 'AES-256-CTR', $key, OPENSSL_RAW_DATA, $iv)实时解密 → 输出Content-Range与206状态码。
第二步:关键头信息必须完整:
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes {$start}-{$end}/{$size}");
header('Accept-Ranges: bytes');
header('Content-Type: video/mp4');
第三步:禁用输出缓冲干扰流数据——在函数开头插入ob_end_clean();,否则ob_flush()会因TP8默认开启的缓冲层而失效。
第四步:HLS分片加密与动态m3u8生成
对高价值课程视频,必须放弃明文TS分片,改用AES-128加密+动态密钥服务。
① 使用FFmpeg预处理视频:
ffmpeg -i input.mp4 -codec:v libx264 -codec:a aac -hls_time 10 -hls_key_info_file keyinfo.txt -hls_playlist_type vod output.m3u8
其中keyinfo.txt内容为:
https://api.example.com/key?vid=123&t=xxx
/data/keys/123.key
1234567890123456
② 创建app/video/controller/Key.php,仅响应GET请求且验证Token:
若$_GET['t']无效或$_GET['vid']未授权,直接http_response_code(403); exit;;否则读取/data/keys/{vid}.key并输出二进制密钥内容,header('Content-Type: application/octet-stream')。
③ m3u8文件不静态存放,由Play.php动态生成并注入当前有效Token到#EXT-X-KEY的URI中,确保每次播放请求都携带新Token。
第五步:关闭ThinkPHP8调试模式并加固路由
开发环境开启app_debug = true会导致错误堆栈泄露绝对路径,攻击者可据此定位/data/videos/真实位置。
执行php think optimize:config生成生产配置缓存→修改config/app.php中'app_debug' => false→在app/video/route.php中显式关闭非法路由匹配:
Route::rule('video/*', 'video/Play/index')->deny();
Route::get('video/play', 'video/Play/index');
Route::get('video/key', 'video/Key/index');
所有其他路径一律拒绝,避免通过路由猜测暴露内部结构。



















