正确设置 content-disposition 响应头需用 attachment 并 rawurlencode 中文文件名;防盗链须用带过期时间的签名 token 而非仅校验 referer;文件须存于 public 外目录且下载走控制器校验。

下载文件时如何正确设置 Content-Disposition 响应头
不设或错设 Content-Disposition,浏览器会直接渲染文本、图片甚至 PDF,而不是弹出下载框。ThinkPHP 默认不自动加这个头,必须手动干预。
实操建议:
- 用
response()显式设置头:return response($content) ->header('Content-Type', 'application/octet-stream') ->header('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"') ->header('Content-Length', strlen($content)); -
filename必须用rawurlencode()处理中文名,否则 Safari 和部分安卓浏览器会乱码或截断 - 避免用
Content-Type: application/download—— 这不是标准类型,IE 会报错,Chrome 会忽略 - 如果文件内容来自磁盘路径(非内存字符串),优先用
readfile()+response()->send(),避免内存溢出
防盗链核心:校验 Referer 不等于“禁止外链”
单纯检查 $_SERVER['HTTP_REFERER'] 是否为空或是否包含自家域名,挡不住 Postman、curl 或 Referer 被主动清空的请求。真要防,得结合服务端签名或临时 token。
实操建议:
- 不要只依赖
Referer:它可被任意伪造或省略,仅适合做初级过滤 - 推荐方案是「带过期时间的下载 token」:生成 URL 如
/download?file=id123&sign=abc&expire=1717020000,服务端验证sign(如md5($file_id . $secret . $expire))和expire是否有效 - ThinkPHP 中可在控制器里统一拦截:
if (!checkDownloadToken(input('get.file'), input('get.sign'), input('get.expire'))) { abort(403, 'Invalid or expired download link'); } - 注意:token 不能暴露在前端 JS 变量里,更不能写死在模板中;应由后端接口动态返回,且有效期建议 ≤ 5 分钟
ThinkPHP 文件下载路径绕过 public 目录的安全风险
很多开发者把上传文件存到 public/uploads/ 下,再用 http://site.com/uploads/xxx.jpg 直链访问 —— 这等于完全放弃防盗链和权限控制,连 ../etc/passwd 都可能被猜解。
实操建议:
- 上传文件务必存到
public外的目录,例如runtime/upload/或storage/app/ - 所有下载请求必须走控制器,禁止直接暴露物理路径
- 若用
think\File读取,注意validateExt()和validateSize()是上传时用的,下载时不生效,需自行校验文件后缀与 MIME 类型是否匹配 - Linux 下注意 open_basedir 限制:如果 PHP 配置了该选项,
file_get_contents()读取runtime/外路径会失败,得同步调整
Response::create() 与 download() 方法的兼容性陷阱
ThinkPHP 6.0+ 提供了 Response::create()->download(),看起来方便,但实际调用链里会自动加 Content-Type,且对中文文件名处理不一致,不同版本行为还不一样。
实操建议:
- 别用
Response::create($path)->download($name)处理非 public 下的文件 —— 它内部会尝试readfile($path),但不校验路径合法性,容易导致任意文件读取 - 6.1+ 版本中,
download()对$name的编码逻辑改成了urlencode(),而 RFC 6266 要求用rawurlencode(),结果 Chrome 下中文名变成空格 - 最稳的方式是弃用封装方法,自己构造
Response实例,手动设头、输出内容、结束脚本:ob_end_clean(); echo $content; exit;
(配合set_time_limit(0)防超时)
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










