2.2 客户端直传:临时密钥而不是服务器中转

业务场景:上传把服务器卡死了

第一版上传做得特别朴素:前端选文件,POST 到我的 CVM 后端,后端再转存进 COS。功能能跑,但每天下午开完会集中上传的时候就遭罪——三个会议室同时传录音,后端带宽被打满,接口超时,纪要出得比人工整理还慢。更揪心的是费用:每 GB 流量过两遍服务器,一份 200MB 的录音要消耗两倍带宽。

那天下班我算了一笔账,决定改成客户端直传:浏览器直接把文件传到 COS,服务器只负责发钥匙、收回调。我们这套底座是照着腾讯云五件套实战搭的,直传改造只动了上传这一环,其他服务一行没改。这一节讲透两个方案的区别、临时密钥怎么用、以及上线前必须过一遍的安全清单。

这套链路里服务器的角色变了,从搬运工变成了发卡器。有一次我给新同事讲架构,用一句话总结:文件走大路直奔仓库,服务器只站在门口发通行证。他听完立刻明白了为什么后端负载曲线在改造后变得平直。

改造成本也要说句公道话:直传不是零代价。后端要加签发接口、前端要接 SDK、调测跨域和时间同步,我大约花了两天。但这两天换来的是服务器不再需要为上传准备带宽和磁盘缓冲,省下的运维心智成本远超两天,更别提之后每次加服务器都要先算上传带宽的中转方案。选技术方案要看总账,不能只看第一版的开发量。

两种上传方式对比

先说结论:除非有强合规要求必须所有流量过服务器,否则录音文件这种大对象一律选直传。

维度 服务器中转 客户端直传
流量费用 过服务器两遍,双倍带宽 只算 COS 入站,服务器零负载
上传速度 受服务器带宽瓶颈限制 走 COS 分布式入口,就近上传
服务器压力 并发大时内存和 IO 都吃紧 服务器只签发密钥,轻量
实现复杂度 简单,后端一把梭 多一个签发接口和前端 SDK
安全性 密钥在服务端,最封闭 临时密钥带作用域,管控得当同样安全
断点续传 要自己实现中间态管理 COS 分片上传天然支持

STS 临时密钥:谁发钥匙、谁能传、传到哪

直传的核心顾虑是:前端要能往 COS 写文件,总不能把永久密钥塞进页面里。腾讯云的做法是 STS 临时密钥,我用一个门禁的比方讲清楚。

永久密钥就像公司大门的实体钥匙,配一把少一把,丢了要换全楼的锁,当然不能发给每个人。STS 临时密钥就像前台发的临时门禁卡:你的后端(前台)拿着自己的永久卡去向 STS 服务申请,说明这张临时卡谁能用、能开哪几扇门(桶和目录前缀)、能用多久(有效期,一般几分钟到一小时)。申请回来是一组临时的 SecretId、SecretKey 和 Token,前端拿到它就能在有效期内、在限定范围内上传,过期作废,就算被抓包也只是开了一张快过期的卡。

几个参数我的实践取值:有效期设 15 分钟,够传一个 200MB 的文件;作用域限定到当天的日期目录前缀,比如 /audio/20261023/,今天领的卡传不了昨天的目录;操作只给上传和分片相关的接口,读取和删除一概不给。

参数取值不是拍脑袋,背后有得失。有效期越长越省事,但泄露窗口也越大;作用域越宽越好对接,但失控风险越高。我把几个关键参数的取舍整理成表:

参数 我的取值 取舍逻辑
有效期 15 分钟 覆盖单文件上传,弱网放宽到 30 分钟
作用域 当天日期目录前缀 精确到目录,越界上传被拦在服务端
允许操作 上传与分片接口 删除、列举权限不给前端
文件大小上限 1GB 公司场景封顶,防盗刷存储
后缀白名单 wav、m4a、mp3 录音桶只认音频,其余拒绝发卡
单用户频率 每分钟 5 次 防止接口被刷成白嫖通道

这张表本身也是动态的:跑过两个月之后,有效期从最初的 30 分钟收紧到 15 分钟,大小上限从无限制收到 1GB,都是被真实事件教育出来的。

直传流程时序

把整条链路拆开看,每一跳传什么都很清楚:

步骤 参与方 传输数据 说明
1 用户 → 后端 会议日期、文件大小 页面加载时先申请上传资格
2 后端 → STS 期望作用域、有效期 后端用自己的永久密钥换取临时密钥
3 STS → 后端 临时 SecretId、Key、Token 带作用域与过期时间
4 后端 → 用户 临时密钥、对象路径 路径由后端生成,前端不自选
5 用户 → COS 音频分片数据 浏览器或客户端 SDK 直传,不经服务器
6 COS → 用户 上传成功回调 携带对象地址与 ETag
7 COS → 后端 上传完成事件通知 后端据此触发转写流程

第 4 步有个细节:对象路径必须后端生成下发,不能让前端自己拼。否则用户可以把文件传到任意目录,权限的作用域就形同虚设。第 7 步是整个工具自动化的起点,后面云函数编排那一篇会展开。

前端集成的三个要点

真正写代码的时候,有三个点把方案和代码连起来。

第一是上传用 SDK 而不是手撸请求。COS 提供了浏览器端的 JS SDK,分片、MD5、重试、进度回调全内置,直接传临时密钥进去就能跑。手撸 multipart 的分片合并和各种边界情况,不值当。

第二是密钥在用到的时候才申请。页面一打开就申请临时密钥是浪费——用户可能选了文件又反悔。点下上传按钮再请求签发接口,15 分钟有效期刚好覆盖这次上传,也不必频繁续期。如果上传超过有效期,SDK 会在快过期时回调通知,前端重新请求一次新密钥续上即可。

第三是进度反馈要做细。分片上传本身每片都有进度,加总之后给用户一个百分比和预估剩余时间,再配上可暂停按钮。上传的体感好坏,八成来自这个进度条,而不是实际速度。

最后一句提醒:直传改造不是一锤子买卖,上线后要持续盯监控。签发接口的调用量、临时密钥的实际有效期命中、上传成功率这三个数,我每周看一次。某次签发接口调用量突然翻倍,顺藤摸瓜发现前端有个页面重复申请密钥的死循环,当天就修了。没有监控的直传,等于闭着眼发钥匙。

改造效果:监控不会撒谎

直传改造上线那天我盯着监控看了一下午。同样的晚高峰集中上传,服务器 CPU 从八十多度降到不到二十度,带宽只动了签发接口那一丁点;再看 COS 那边,上传并发比之前更高,完成时间却提前了半小时。同事们的反馈只有一句:今天快得离谱。那一刻我确认,这一步改对了。

安全清单

上线前我逐条核过一遍,建议你也照着过:

第一,前端绝不放固定密钥。不管什么理由,SecretKey 出现在前端代码里就是事故,git 历史里翻得到、浏览器调试里看得到。临时密钥随用随申请,用完即弃。

第二,密钥有效期设到刚够用为止。我们传一个文件 15 分钟,弱网下放宽到 30 分钟,绝不超过一小时。有效期越短,泄露后的可控窗口越小。

第三,桶权限最小化。临时密钥的作用域收敛到目录前缀级别,操作收敛到上传必需的接口,删除和列举权限一律不给前端。

第四,后端的签发接口加登录态校验。临时密钥是按用户发的,如果接口本身不鉴权,任何人都能要卡,作用域再细也挡不住刷量。

第五,上传加大小和类型白名单。签发接口里校验声明的文件大小和后缀,录音桶只允许音频格式,别让桶变成公共网盘。

第六,临时密钥下发就走 HTTPS,别图省事明文传。密钥在传输途中被抓包,等于门禁卡被复制,HTTPS 是成本最低的一道防线。

第七,给签发接口记日志。谁、在什么时间、为什么文件名申请了密钥,落到日志里,真出事的时候这是唯一的排查线索,也是安全复盘时的证据链。

第八,定期轮换后端永久密钥。临时密钥的天花板就是后端那把永久锁,后端密钥按季度轮换一次,即使泄露也有自动截止日期。

这八条清单看着多,前五条是上线必须,后三条是长效机制。安全这个事,从来不是一次性的动作,而是一套要长期维护的习惯。

踩坑与解法

有一次同事在咖啡厅用网页版上传,报错提示签名不匹配,但同一文件回公司就好了。查下来是本机时间偏差导致签名时间戳校验失败,前端 SDK 默认用本机时间。解法是页面初始化时从服务器同步一次时间差,签名时用校正后的时间。另一个坑是 CDN 缓存把签发接口的结果缓存了,导致不同用户拿到同一个临时密钥,给那个接口加上禁止缓存的响应头才消停。

还有一坑更隐蔽:签发接口最初没校验文件大小,有同事把一个 2GB 的视频也想往录音桶里塞,差点把存储费用刷上去。这之后我把大小和后缀校验从清单落进了代码,超限直接拒绝发卡。另外整个上传站点的部署细节,比如 HTTPS 证书和跨域配置,可以参考轻量建站实战,直传对跨域头的要求那本书里也讲过。

延伸阅读