验证规则必须包含file,否则mimes、max等失效;需用isValid()或validate()确保文件真实上传;mimetypes比mimes更安全;存储时须过滤原始文件名防路径遍历。

验证规则必须用 file 作为基础类型
不加 file,其他规则如 mimes 或 max 会直接被忽略。Laravel 只有在明确知道字段是上传文件时,才启用文件专属验证逻辑。
常见错误是写成 'avatar' => 'required|mimes:jpg,png|max:2048' —— 这里漏了 file,导致验证形同虚设,恶意用户可绕过类型检查传任意文件。
-
'avatar' => 'required|file|mimes:jpg,png,gif|max:2048'✅ 正确写法 -
'avatar' => 'required|file|mimetypes:image/jpeg,image/png,image/gif|max:2048'✅ 更严格,校验真实 MIME(非扩展名) - 若同时需要校验多个文件,用数组语法:
'files.*' => 'required|file|mimes:pdf,docx|max:5120'
store() 前必须先调用 isValid() 或走 validate()
$request->file('avatar')->isValid() 是底层安全兜底:它调用 PHP 的 is_uploaded_file(),防止攻击者伪造 $_FILES 数组绕过表单上传流程。
虽然 validate() 内部已自动调用 isValid(),但如果你手动取文件(比如用 $request->avatar 或 $request->file('avatar')),就必须显式判断:
- 没验证就直接
->store(),可能把临时目录外的任意路径文件“移动”进来,造成路径遍历或覆盖风险 - 正确顺序:
if ($request->hasFile('avatar') && $request->file('avatar')->isValid()) { ... } - 更推荐统一走
$request->validate(),既简洁又自带isValid()和类型/大小检查
mimes 和 mimetypes 的区别直接影响安全性
mimes 只检查客户端提交的扩展名(易伪造),mimetypes 才真正读取文件头识别 MIME 类型(需 PHP fileinfo 扩展启用)。
例如用户把木马文件重命名为 photo.jpg,mimes:jpg 会放行,mimetypes:image/jpeg 则拒绝。
- 生产环境务必同时用两者:
'file' => 'required|file|mimes:jpg,jpeg,png|mimeTypes:image/jpeg,image/png' - 确认
fileinfo已启用:php -m | grep fileinfo - ZIP 类型要特别注意:
mimes:zip不防伪,必须搭配mimetypes:application/zip,且后续还需用ZipArchive解包校验内部结构
验证通过后,别直接用 getClientOriginalName() 存文件
这个方法返回的是用户浏览器传来的原始文件名,含路径分隔符(如 ../../etc/passwd)、空格、Unicode 控制字符,甚至 null 字节——直接拼进 move() 或 storeAs() 可能导致目录穿越、覆盖系统文件或存储失败。
正确做法是剥离、重命名、限制长度:
- 用
$file->getClientOriginalExtension()取扩展名,再拼上唯一前缀:uniqid() . '.' . $file->getClientOriginalExtension() - 或彻底忽略原始名,用
$file->store('avatars')让 Laravel 自动生成安全哈希名 - 若业务强依赖原始名(如合同归档),至少过滤掉危险字符:
preg_replace('/[^a-zA-Z0-9_.\-]/', '', $original)
最易被忽略的一点:验证和存储之间不能有逻辑断层——比如验证用 public 磁盘,存储却误写成 local,结果文件存到不可访问位置,前端 URL 拿不到;或者验证允许 pdf,但存储后没配好 storage:link,导致生成的 Storage::url() 返回 404。


















