尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

分片传输+动态签名:银行培训视频防下载的PHP实现方案

发布时间:2026/9/28 18:26:13

资讯中心
01
ARTICLE

分片传输+动态签名:银行培训视频防下载的PHP实现方案

分片传输+动态签名:银行培训视频防下载的PHP实现方案
两年前我接了一套银行内部远程培训系统的安全改造对方提了个很直接的要求几千名员工要异地在线看视频但视频内容绝不能变成一个完整文件流出去。当时市面上大多数网盘和网课系统都宣传自己能“防下载”真拿去实测就会发现要么浏览器开发者工具一开就能抠出MP4直链要么屏幕录制软件一挂就能整段带走。后来我们改用“分片传输”的思路重新设计整套链路核心就三件事前端用HTML播放器做容器服务端用PHP做鉴权和加密分片下发再叠加一套动态签名、水印和设备指纹机制做兜底。这套方案在真实业务里跑了一年多总体效果很稳我把完整思路、关键实现和踩过的坑都整理在下面。这篇文章适合谁看如果你在企业内部搞培训系统、知识库、金融行业的内容安全平台或者正准备改造一套不允许学员把视频下载下来的系统本文可以给你一套能直接落地的参考框架。不过在动手之前你需要先接受一个基本观点防泄漏的本质不是加一道锁而是把整个分发链路拆成一段一段的控制点。1. 远程培训视频在银行的泄漏风险远比你想象的严重1.1 视频内容与普通课件不是一个量级银行的远程培训视频内容敏感度往往被低估。表面上是“新员工入职培训”“反欺诈案例讲解”实际画面里出现的经常是内部业务系统的真实界面、网点现金作业流程、核心系统升级操作演示、甚至某个阶段尚未对外公布的新业务规则。这些内容一旦离开内网环境就是给别人画了一张银行内部工作流地图配合常规社工手段价值比几句“请勿外传”的标语高得多。我遇到过最典型的一个场景某分行为了赶上线进度把新核心系统操作录屏视频传到一个通用网盘再用默认分享链接发给十几个网点。不到一周这个链接就被转发了上百次。你说这是技术漏洞吗不完全是关键是共享链路里没有任何一层针对视频内容本身的控制。传统文档还能做权限管理和加密视频却因为要能播放天然就得把数据传到终端本地这个矛盾是所有视频防泄漏系统的出发点。1.2 传统单文件分发的漏洞出在哪常见的实现方式是后台管理员传一个MP4上去前端用一个video标签播放。登录鉴权做了播放权限也做了看起来没问题。但对稍微有点经验的使用者来说开浏览器F12看Network面板就能在媒体请求里找到真实的播放地址有的系统甚至直接暴露原始文件路径。拿到地址之后用下载工具拉下来的就是一个完整MP4后面再去水印、转码、二次上传基本就脱离了平台控制。这里要澄清一个误区很多人以为“播放器右键禁掉”“不让下载”就是防泄漏了。其实这些只能防住不会技术的普通用户。我们做安全改造时默认的威胁模型是“对方愿意花十分钟研究你的前端代码”。只要传输对象本身是一个完整文件那防下载就只是纸面功夫。拿纸面阅读来类比你把一本书放在阅览室却在桌上直接放了一台连着复印机的整本原稿还指望靠“请勿复印”四个字挡住别人怎么可能。1.3 威胁模型内部人员才是大头这套系统做完前我们一起统计过历史泄漏事件结论和很多人想的不一样外部黑客攻击拿到弱口令并批量下载只是少数。真正高发的场景是内部员工离职前有意获取资料通常在离职前两周集中下载。外包人员或第三方驻场员工把视频当作“学习资料”随手转发。部门之间的账号共用导致事后完全无法定位泄漏源头。员工在公开平台求助“有没有XX课程最新版”顺手把内部视频挂上去交换资源。所以方案设计上不能只防外部攻击更要考虑“一个拿着合法账号的内部人员他在系统中最多能做哪些事”。分片传输的核心价值在这里就体现出来了就算账号完全合法学员在任何一个时刻能拿到的也只是一小段加密数据很难拼出一份完整视频。这相当于把“一次性获取全部资源”的动作变成了“必须持续实时在线才能完成的行为”而持续在线行为是可审计、可中断、可追踪的。2. 分片传输的整体设计让“完整文件”根本不存在2.1 核心安全模型从“防下载”到“无完整文件”改造之前我们内部先统一了一个概念真正的防泄漏不是“下载不了”而是“根本没有一个完整文件可以被下载”。传统在线播放系统把视频当成一个资源对象来分发网络里传的是完整文件你在终端缓存里也能找到结构完整的MP4。分片传输系统则完全反过来。原始视频在入库后会被转码器切成大量片段每个片段单独加密再保存到内容库。学员端播放器拿到的是一个“播放清单”明确告诉它先去请求第几段、用哪个临时密钥、从哪里取下一段。只有按时按序把密文分片拉到前端并逐段解密画面才能连续播放。任何单次网络请求拿到手的数据都是一堆互不连续的密文碎片单独保存下来没有意义。这里有一个很关键的原则不要只做“切分”还要做“加密动态签名”。我见过有些号称分片传输的系统其实就是把视频切成普通的TS分片文件放在目录里前端用HLS标准直接拉流。结果所有分片文件名按编号排列请求里还没有鉴权信息攻击者用脚本几分钟就能把所有分片下载合并。这等于把完整文件从一个变成了几百个更小的完整文件安全等级反而更低。分片本身不是安全措施分片之后每一片仍然有独立鉴权、独立加密、独立失效时间才构成安全模型。2.2 系统组件与一次完整的播放流程整套系统里主要组件和职责如下组件职责转码切片服务将原始视频转成统一编码格式按时长切片逐段加密内容存储库只保存加密后的分片文件和元数据不落明文PHP应用服务登录态校验、权限判断、动态签名生成、分片下发、频控风控前端播放器用HTMLJavaScript请求分片、解密、组装画面、叠加水印日志与风控模块记录每次请求的来源、设备指纹、IP、时间做异常行为分析一次完整的播放流程可以简化为六步视频导入系统后转码服务把原始MP4切成固定时长的分片并逐一加密。学员登录平台前端向后端请求播放令牌。PHP校验学员身份、课程权限、设备信息返回一个短时效的会话令牌。前端根据播放清单逐个携带签名参数请求分片密文。PHP校验签名、时效、频控通过后才是流式回传密文数据。前端收到密文后立即解密灌入播放器缓冲并渲染播放结束时清空内存。每一步都带日志。后面真要出泄漏事件拿时间线一叠基本能定位到具体账号和设备。2.3 分片时长的选择与参数对比分片时长是第一个需要拍板的参数。太长单片段数据量太大一旦暴露损失也大太短请求量和日志量暴涨服务端压力集中。我们在不同项目里试过 2 秒、5 秒、10 秒三档对比如下。分片时长90分钟视频片段数优势代价2秒约2700片单片段价值最低泄露一片几乎无用请求量巨大日志与带宽开销高5秒约1080片请求量适中安全与体验平衡需要良好的并发与预取策略10秒约540片服务端压力小一旦加密被破解单片段价值偏高我通常建议优先选5秒左右。大多数浏览器对视频请求的处理能力很成熟5秒一片能让播放器保持连续缓冲又不至于把后端打成接口风暴。至于更安全的1秒分片除非视频内容涉密度极高且系统基础设施足够扛得住否则不要优先选成本翻倍但不一定带来对等的体验和安全收益。3. PHP服务端的鉴权、加密分片与动态签名3.1 数据表怎么设计后端用PHP做主服务数据库建议至少包含四张表视频表、分片表、播放令牌表、访问日志表。下面是精简后的核心表结构样例。CREATE TABLE video ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, status TINYINT DEFAULT 1, created_at INT UNSIGNED NOT NULL ); CREATE TABLE video_segment ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, video_id INT UNSIGNED NOT NULL, index_no INT NOT NULL, encrypted_file VARCHAR(255) NOT NULL, cipher_key_id INT UNSIGNED NOT NULL, duration DECIMAL(5,2) DEFAULT 5.00, UNIQUE KEY uk_video_index (video_id, index_no) ); CREATE TABLE play_token ( token VARCHAR(64) PRIMARY KEY, user_id INT UNSIGNED NOT NULL, video_id INT UNSIGNED NOT NULL, expires_at INT UNSIGNED NOT NULL, bind_ip VARCHAR(64) DEFAULT , device_fingerprint VARCHAR(128) DEFAULT ); CREATE TABLE access_log ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, user_id INT UNSIGNED NOT NULL, segment_id INT UNSIGNED NOT NULL, ip VARCHAR(64) NOT NULL, user_agent VARCHAR(255) DEFAULT , device_fingerprint VARCHAR(128) DEFAULT , created_at INT UNSIGNED NOT NULL, KEY idx_user_time (user_id, created_at) );分片表里只存放密文文件的路径和密钥ID不存明文视频内容。播放令牌表用于管理“这次播放会话允许访问哪些分片”这个设计比单纯依赖cookie更可控因为每次播放都会生成独立令牌后端可以在服务端主动失效它。3.2 播放令牌的签发逻辑这是所有鉴权动作的第一道闸门。前端在拿到视频列表后发起请求后端PHP收到请求后做三件事验证登录态和课程权限检查是否超过用户同时在线播放数限制生成一个随机token绑定uid、video_id、过期时间和设备指纹再返回给前端。play_token的过期时间不宜太短否则播放中途失效会造成体验中断也不宜太长否则令牌被复制后长时间有效。我自己一般设成两小时并允许播放器在播放过程中通过心跳接口续期。如果用户切换IP或设备指纹变化后端会直接拒绝分片请求防止令牌被拿到其他终端复用。3.3 分片下载接口与签名校验代码分片接口是整个系统最核心的一环。每个分片请求的URL都带上了动态签名和时间戳格式类似/api/segment/1023?uid1001expires1780000000signe9a3c7f1b8d22e6f签名生成逻辑我放在服务端统一处理把分片ID、用户ID、过期时间拼成字符串用HMAC-SHA256做一次哈希。前端拿到的签名是在请求令牌阶段一起下发的每次播放都不同无法提前批量构造。PHP端校验过程大致如下public function downloadSegmentAction() { $segmentId (int)$this-request-get(segment_id); $uid (int)$this-request-get(uid); $expires (int)$this-request-get(expires); $sign $this-request-get(sign, ); // 1. 过期校验允许60秒时钟偏差 if ($expires time() - 60 || $expires time() 3600) { http_response_code(410); exit; } // 2. 签名校验 $expected hash_hmac(sha256, $segmentId . : . $uid . : . $expires, SECRET_KEY); if (!hash_equals($expected, $sign)) { http_response_code(403); exit; } // 3. 校验播放令牌与设备绑定 $token $this-request-get(token); if (!$this-checkPlayToken($token, $uid, $this-deviceFingerprint())) { http_response_code(403); exit; } // 4. 频控校验 if (!$this-rateLimit($uid, $this-clientIp())) { http_response_code(429); exit; } // 5. 流式输出密文文件 $path $this-loadEncryptedFile($segmentId); header(Content-Type: application/octet-stream); header(Content-Length: . filesize($path)); header(Cache-Control: no-store); $fp fopen($path, rb); while (!feof($fp)) { echo fread($fp, 8192); ob_flush(); flush(); } fclose($fp); }这里要特别注意一点不要用file_get_contents()把整个分片读进内存再输出。虽然单片段通常只有几百KB但PHP默认的内存限制是128M当并发量上来、又有多个大分片同时请求时一次性读取会导致内存迅速打满。流式输出配合fread分块读取是基本操作还能让第一个字节更快到达前端对起播速度有直接帮助。3.4 存储加密别把密钥和分片放一起分片文件在磁盘上必须是密文。我们用的是AES-256-CBC加密转码切片服务在生成分片文件之后立刻加密明文数据只在加密进程的内存里短暂停留随后删除。每个分片有一个独立的随机IV加密参数和密钥ID记录在分片表里。密钥管理方面强烈不建议把密钥硬编码在PHP文件里。当时我们使用的是一套独立密钥服务PHP代码运行时通过内部接口按密钥ID获取密钥内存中用完即释放。这样即使某个分片文件被打包拖走没有对应的密钥仍然无法还原内容。密钥服务应独立部署网络策略上也应做到只允许应用服务器访问。还要提醒一句很多团队喜欢把密钥和分片文件放在同一个服务器的同一个目录里觉得省事。真出了事这就是毁灭性的。分片是密文、密钥是钥匙钥匙和锁放在同一个抽屉里等于不上锁。我见过厂商在演示时把密钥写死在配置里最后安全审计被一票否决就是这个原因。4. HTML前端播放器把加密分片安全拼回画面4.1 用MSE接管播放而不是暴露MP4地址前端技术选型上最合适的方案是使用MediaSource ExtensionsMSE。MSE允许JavaScript程序逐段创建数据源动态往视频缓冲区追加小片段。这样video标签的src不再指向一个MP4地址而是一个通过URL.createObjectURL()生成的临时对象地址。核心逻辑可以这样理解浏览器已经不知道“这个视频文件完整长什么样”只知道有一个人在不断给它一小块一小块的解码数据。就算用户在Network面板里翻遍所有请求看到的也只是分片接口返回的密文流而不是xxx.mp4这样一眼就能下载的文件名。前端请求分片并组装的核心代码骨架如下video idplayer controls autoplay muted/video script const video document.getElementById(player); const mimeCodec video/mp4; codecsavc1.42E01E, mp4a.40.2; if (!window.MediaSource || !MediaSource.isTypeSupported(mimeCodec)) { alert(当前浏览器不支持安全播放器请使用Chrome或Edge); throw new Error(MSE unsupported); } const mediaSource new MediaSource(); video.src URL.createObjectURL(mediaSource); mediaSource.addEventListener(sourceopen, async () { const sourceBuffer mediaSource.addSourceBuffer(mimeCodec); const res await fetch(/api/player/token, { method: POST, headers: {Content-Type: application/json}, credentials: include, body: JSON.stringify({video_id: 1001}) }); const manifest await res.json(); // manifest.segments 是按播放顺序排列的分片元数据 for (const seg of manifest.segments) { const resp await fetch( /api/segment/ seg.id ?uid manifest.uid expires manifest.expires sign manifest.sign token manifest.token, {credentials: include} ); const ciphertext await resp.arrayBuffer(); const plaintext await decryptSegment(ciphertext, manifest.sessionKey); await appendSourceBuffer(sourceBuffer, plaintext); } mediaSource.endOfStream(); }); /script这里面的decryptSegment是业务自定义函数内部可以用Web Crypto API的SubtleCrypto.decrypt实现AES解密。appendSourceBuffer需要处理SourceBuffer更新状态竞争标准做法是监听updateend事件或者用一个Promise队列串行追加避免多次调用appendBuffer引发异常。有一点要坦白说MSE方案没法做到“绝对无法转为文件”。因为解密后的明文数据最终必须喂给解码器有权限的终端理论上总可以拿到某种中间产物。MSE真正的价值是大幅提高获取成本并且让常规用户没有“一条直链下载整个视频”的机会。4.2 分片请求与内存回收在线播放是一个持续过程如果前端把所有分片都预取到内存视频还没播完浏览器内存已经爆了。处理方式是分片按需请求同时只预取未来几个片段并在当前播放位置跨越一定距离后把已经播放过的分片从SourceBuffer中移除。我倾向于让前端维持两个缓冲区一个“待下载队列”负责拉取未来3-5个分片一个“已解码缓冲区”控制在几十秒内。用并发数限制比如最多同时请求4个分片避免网络抖动导致排队卡顿。实测下来5秒一片、预取5片的方式在普通办公网络下既能保证秒开也能把内存控制在合理范围。播放结束后立即执行三件事停止所有在途请求、清空SourceBuffer、调用URL.revokeObjectURL()释放对象地址。我见过不少实现遗漏了最后一步导致播放器反复打开后页面越来越卡其实就是Blob对象没有被及时释放。4.3 你以为的“隐藏”和真正的边界有人说既然分片请求都带着签名参数让Network面板里看起来全是乱码不就“隐藏”得很好吗不是这样。任何在浏览器里加载的数据开发工具都能看到请求头和响应内容。分片传输不是魔法它防的是“下载后另存和合并”不是防“查看网络流量”。所以宣传上不要把安全期望定得太高要向内外部说清楚技术手段把“完整文件下载”变成“高成本行为”而真正要锁定责任、形成威慑靠的是后面的水印和指纹体系。分片传输解决的是“能不能一下拿到手”水印体系解决的是“就算拍屏录屏也能找到是谁干的”两者结合才是完整闭环。5. 水印、指纹与录屏对抗泄漏之后的追踪5.1 动态可见水印成本最低、威慑最大前端播放器里动态水印是我们项目里投入产出比最高的一环。实现很简单在video元素上方叠加一层半透明的DOM层把当前用户的姓名缩写、工号、设备指纹、当前时间渲染成文字位置随机、透明度控制在5%到10%不影响观看又无法被简单抹掉。动态的含义是水印会随时间变化比如每隔几十秒切换一次展示信息或者让水印缓慢移动。这样就算有人用手机对着屏幕拍一段视频里的水印信息也在变化事后定位时间片段更容易。实际使用中要避免一个常见错误把水印做成固定位置的静态徽标。这种水印在视频处理软件里用裁剪或模糊就能去掉等于白做。动态水印因为位置和内容都在变处理成本高很多而且和处理者自己的设备指纹绑定威慑力完全不一样。5.2 隐性水印与设备指纹可见水印防的是截屏、翻拍但如果有人把视频转录后重新压缩上传可见水印很容易被二次编辑抹掉。对更敏感的内容我建议增加不可见水印即在转码阶段把用户ID和设备标识编码进视频帧的频域里。这项技术实现复杂度高一般建议采购成熟方案或做合作开发。设备指纹计算则是配合访问日志的重要手段。前端通过Canvas指纹加字体检测生成一段短字符串作为当前设备的标识在请求令牌和分片时提交给后端。最大用处是同一账号在不同设备播放时后端能迅速判断出异动。我们曾经靠这个抓到过一个外包人员把自己账号借给同事的情况两套完全不同的设备指纹同时出现在一个账号下风控模型立刻报警。5.3 录屏防护的边界我选择“能接受”而不是“万能”做了这么多之后必须跟你说句实话浏览器层面无法完全禁止录屏。不管是Windows的录屏工具、macOS的屏幕录制还是拿第二台设备对着屏幕拍都不是前端代码能根治的。方案设计上不要把“防范录屏”当成唯一目标而是要分层次看问题。层级手段预期效果阻断层分片传输、动态签名、加密存储防止直接下载完整文件降险层动态水印、播放器失焦暂停、禁止全屏降低随手录屏和翻拍的动机追踪层设备指纹、访问日志、不可见水印泄漏后能快速定位到人威慑层制度审查、账号权限回收流程用明确追责约束内部人员我当时和客户达成的共识是系统不承诺“录不下来”而是承诺“录了能查到人”。这个定位看着保守实际运行效果却很好因为在内部场景里追责能力本身就是最硬的威慑。与其在水印技术上堆到极致不如把基础分片、追踪、制度三件事都做到位。6. 落地过程中踩过的坑与参数取舍6.1 PHP内存、大文件输出和签名时钟第一个坑就是PHP内存。刚开始我们图省事分片接口里用了file_get_contents单用户播放没问题压测到50并发时内存直接飙升进程频繁被OOM干掉。改成fopenfread流式输出之后内存占用从几百MB降到了十几MB效果立竿见影。第二个坑是签名校验里的时间问题。用户浏览器时间和服务器时间不一致时前端生成的请求时间戳经常偏大或偏小导致合法请求被判过期。解决方案是签发token时不直接用前端时间而是让前端先调一个时间同步接口获取服务器时间偏移量后续请求时间戳都基于这个偏移计算。校验时再放60秒容差基本能覆盖绝大多数场景。第三个坑是关于PHP临时文件的。调试时发现某些环境下分片文件被fread读取后频繁出现File not found排查半天发现是缓存层把文件清掉了。因此文件读取要做好异常兜底文件不存在时返回404并记录告警而不是直接抛一个赤裸裸的PHP错误页面。6.2 客户端兼容性Safari、缓存与CDNMSE在Chrome、Edge、Firefox上表现稳定但Safari一直是老大难。它对video/mp4格式的SourceBuffer支持有历史问题分片封装格式稍不对就黑屏。我们最终的折中方案是所有视频统一转成H.264AAC封装的fMP4分片确保mp4 codecs字符串在主流浏览器都能识别。Safari端再嵌套一层兼容判断不支持MSE时自动降级到原生HLS播放但同时关闭缓存和下载能力安全强度略降但保证可用。缓存策略也值得注意。分片接口要明确返回Cache-Control: no-store否则浏览器和CDN会把密文分片缓存下来绕过签名校验。CDN层面如果要用建议开启独立鉴权配置至少做到URL过期和Referer白名单。我们在生产环境就遇到过CDN把分片缓存后直接请求缓存URL也能拉流的案例排查半天才发现是缓存头配置漏了。6.3 日志、令牌复用和运营管理的心得访问日志一定要做但不能无脑全存。分片请求一天可能上百万条全部长期保存会拖垮数据库。我们的做法是正常访问日志保留7天异常告警日志保留30天涉及泄漏事件的日志单独导出归档。日志表按天分表查询时指定user_id和时段索引速度完全够用。播放令牌的复用必须盯紧。令牌一旦签发后端要能感知“同一时间同一账号在多地播放”。我们实现了简单的互斥策略同一用户启动新播放会话时旧token自动作废并生成一条风控日志。这个策略看似激进但效果很好内部账号共用情况明显减少。当然为了照顾多个终端的合法需求比如电脑和Pad同时看可以再扩展成“允许最多两个并发会话”具体看业务容忍度。最后一个心得是运营层面的技术方案再完善也需要培训组织方配合流程。我们上线后给培训管理员开了一次简单的会话把“为什么不能直接发MP4链接”“为什么水印上有工号”讲清楚配合意愿立刻提高。反过来如果一个内部平台的防泄漏措施连管理员自己都觉得麻烦落地效果一定打折扣。如果现在让我重新做一遍这个系统我会把更多预算花在追溯能力和播放体验上而不是死磕“防止录屏”这种不可能做到100%的方向。分片传输 动态水印 严格令牌管理这套组合在上线后的绝大多数场景里都足够可靠。真正能守住企业内容安全的往往不是某一个炫技技术而是一套让下载变得昂贵、让泄漏变得可追溯、让操作变得必须有代价的完整链路。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。