最直接有效的方式是在<input type="file">中设置accept属性,如accept="image/*"或具体类型列表,但需配合JavaScript二次校验file.type与扩展名,并在服务端通过magic bytes严格验证。

用 accept 属性声明图片 MIME 类型
最直接有效的方式是在 <input type="file"> 中设置 accept 属性,浏览器会据此过滤文件选择对话框中显示的文件类型。注意它只影响 UI 层面的筛选,不提供安全校验:
-
accept="image/*"允许所有常见图片格式(jpg、png、gif、webp、avif等) - 若需更精确控制,可列出具体类型:
accept="image/jpeg,image/png,image/gif" - 避免写成
accept=".jpg,.png"—— 扩展名匹配不可靠,部分系统(如 macOS)不按扩展名识别文件类型 - 某些旧版 Safari 对
image/*支持不完整,建议至少补充image/jpeg和image/png
用 JavaScript 检查 File.type 和 File.name
用户可能绕过文件选择器(如拖放、命令行上传),必须在 JS 中二次校验。仅靠 type 不够可靠,需结合扩展名和类型检测:
-
file.type由浏览器根据文件头或扩展名推测,可能为空或错误(例如把.jpg文件重命名为.txt) - 应同时检查
file.name.toLowerCase().endsWith('.jpg')等扩展名,但不能单独依赖它 - 更稳妥的做法是:先用
file.type.startsWith('image/'),再辅以白名单扩展名验证 - 示例逻辑:
const isValidImage = (file) => { if (!file.type.startsWith('image/')) return false; const ext = file.name.split('.').pop().toLowerCase(); return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif'].includes(ext); };
服务端必须校验,前端限制只是用户体验优化
前端一切限制都可被绕过。如果没在服务端验证文件类型,攻击者可上传任意文件(如 PHP 脚本),造成严重安全风险:
- 不要信任
Content-Type请求头,它可被轻易伪造 - 应读取文件前几个字节(magic bytes)判断真实类型,例如
ff d8 ff是 JPEG,89 50 4e 47是 PNG - Node.js 可用
file-type库,Python 可用python-magic,PHP 可用finfo_file() - 即使通过类型校验,也务必重命名文件、保存到非 Web 可执行路径,并禁用目录遍历(如拒绝
../路径)
移动端 iOS Safari 的特殊行为
iOS 上 accept="image/*" 有时无法触发相机选项,或默认只显示“照片”而非“相机”:
立即学习“前端免费学习笔记(深入)”;
- 添加
capture="environment"或capture="user"可强制调起摄像头(但仅限单图) - 若需支持多图 + 相机 + 相册,建议保留
accept="image/*",并移除capture——iOS 16+ 已改善兼容性 - 测试时发现某些版本 Safari 忽略
multiple与accept组合,可降级为单选 + 用户手动多次上传
accept 是第一道门槛,JS 校验是第二道,而服务端 magic bytes 检查才是真正的防线。三者缺一不可,尤其别因前端看着“生效了”就跳过服务端验证。



















