文件上传与静态资源
multipart 上传、大小类型限制、存储与 URL 暴露的完整往返
- 理解 multipart/form-data 为什么不能用 JSON 传文件
- 掌握上传时的大小与类型限制
- 能识别路径穿越与类型伪造两类上传漏洞
- 了解存本地磁盘 vs 对象存储 + CDN 的取舍
前台收包裹(接收上传字节)→ 扫码存进储物间(落盘或对象存储)→ 给你一个取件码(返回访问 URL)。文件上传接口就是这条流水线,少一环都拿不到图。
文件为什么不走 JSON?
JSON 只能塞文本,二进制图片直接塞进去会体积膨胀三倍还容易乱码。上传文件要带文件名、文件类型、二进制内容,HTTP 用 multipart/form-data 这个格式把它们分段打包。
application/json:传结构化文本,登录、增删改查都用它。multipart/form-data:传文件 + 字段,头像、附件、图片上传用它。选择口诀:带二进制文件就用 multipart,其余用 JSON。
上传校验:大小与类型
开放上传等于开放别人往你服务器塞东西,必须限制:大小上限(比如头像 2MB)类型白名单(只允许 png/jpg/gif)别信前端传的后缀,后端按字节内容复核。下面用纯 Python 模拟接收与校验。记住这三条都必须在服务端做,前端校验只是给正常用户省一次请求。
上传漏洞:别信前端说的任何话
开放上传接口是攻击者最爱打的入口之一,因为它直接决定别人能往你服务器写什么文件。两个经典漏洞必须知道。
路径穿越(directory traversal):前端把文件名传成 ../../../var/www/evil.php,后端图省事直接拼接路径落盘,文件就被写到了网站根目录,等于对方在你服务器上种了一个 WebShell,之后能远程执行命令。正确做法:永远不要用用户传来的原始文件名拼磁盘路径,自己生成一个随机文件名(上面代码里 avatar_随机数.ext 就是这个思路),落盘目录和 Web 根目录严格分开。
文件类型伪造:攻击者把一个恶意 .php 文件后缀改成 .png 上传,后端只看后缀就放行,文件被解析执行。正确做法:除了看后缀白名单,还要读文件头几个字节(魔数)判断真实类型——PNG 文件头固定是一串魔数,伪造后缀改不了它。生产上再叠一层异步病毒扫描兜底。
还有一条铁律:前端校验只是体验,不是安全。浏览器里的『禁止选择 .exe』随便用 Postman 就能绕过,所有大小、类型、数量限制必须在后端再做一遍。很多事故都是『前端禁了、后端忘了拦』。另外别用用户原始文件名落盘——两个人同时上传同名头像会互相覆盖,自己生成随机名还顺手解决了这个问题。
第一道:自己生成文件名,绝不用用户原始名拼路径(防路径穿越)。第二道:读文件魔数 + 后缀白名单,不信前端声明的类型(防类型伪造)。第三道:上传后异步病毒扫描,可疑文件隔离不对外暴露。隐私文件(合同、证件)别放公开读的桶,要用带过期时间的签名 URL,下载完即失效。三道闸各管一段,缺一不可。
存哪?怎么暴露?
存本地磁盘:小项目最快,后端把 uploads 目录挂成静态路由,通过 /static/xxx 直接访问。但多台服务器之间文件不共享,服务器重装文件就没了。
对象存储(如腾讯云 COS、阿里云 OSS,价格与额度以官方最新为准):文件传上去就给一个公网 URL,配合 CDN 分发。生产环境头像、附件基本都走对象存储。
# 完整往返(静态示意)
# 1. 前端:<input type="file"> 选头像,FormData 提交
# 2. 后端:接收字节 → 校验大小类型 → 存盘/传对象存储
# 3. 后端:返回 { "url": "https://cdn.xxx.com/avatar_123.png" }
# 4. 前端:<img src={url}> 直接显示大文件分片与 CDN 回源
传一个 2GB 的视频,一次性上传中途断网就得从头再来。分片上传思路:前端把文件切成 5MB 一小块一小块传,每片带序号和总片数,后端收齐后按序号合并;中途断了只重传缺的那几片,最后一片传完触发合并。这是网盘、视频站的标配。
对象存储 + CDN 配合的回源逻辑:文件传到 COS/OSS 后,公网 URL 前面套一层 CDN。用户访问图片时先找离他最近的 CDN 边缘节点,有缓存直接返回;没有才「回源」去对象存储拉一次并缓存下来。这样热门头像被成千上万人看,压力全在 CDN 上,你自己的源站带宽几乎不花钱。
头像这种小图还有个工程细节:前端选完图先在浏览器里压缩缩放到合适尺寸再上传,能把一张 10MB 的手机照片压到 200KB 以内,上传快、存储省、加载也快。别让用户原图直传——那是对两边带宽的浪费。分片上传同理,传完一片就记一下进度,刷新页面能从断点继续,不用从头来。另外每个用户最好设每日上传配额,防止有人用你的存储当免费网盘。
下一个全栈毕业项目要做用户头像上传,这一节就是它的零件。真实 FastAPI 里用 UploadFile 接收,逻辑和上面模拟的完全一样。
前端上传用户头像时,请求头的 Content-Type 应该是?
攻击者上传文件名 ../../../var/www/evil.php,后端直接拼接落盘导致 WebShell。这属于哪种漏洞?
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
穿透是反复查「根本不存在」的数据,用布隆过滤器或缓存空值挡;击穿是某个热点 key 过期瞬间大量请求压向数据库,用互斥重建或逻辑过期;雪崩是大批 key 同时过期,给 TTL 加随机抖动。缓存与库的一致性常用「先更新数据库、再删除缓存」,并靠重试/订阅补偿。
挑战任务
文件超限闸门
场景:你的上传接口经常被人传几个 GB 的垃圾文件打爆。先写 check_size(size, limit) 拦住超大数据——size 不超过 limit 放行返回 True,否则拒绝返回 False。
课后作业
设计头像上传规则
场景:你的头像上传接口被人传过后门文件。请列出你的四道规则——大小上限、允许格式、存储位置(本地还是对象存储)、以及防路径穿越/类型伪造各怎么落地。