直接返回文件路径不安全,会暴露服务器结构、绕过权限校验、引发爬虫下载;应使用FileStreamResult(MVC)或HttpResponseMessage+StreamContent(Core),配合路径校验、Content-Disposition设置及流式读取,杜绝物理路径泄露。

为什么直接返回文件路径不安全
用户能拿到真实 URL,就等于暴露了服务器文件结构、可能绕过权限校验、甚至被爬虫批量下载。用 Response.TransmitFile 或直接写入 Response.OutputStream 都会泄露物理路径(比如 C:\inetpub\wwwroot\files\report.pdf),日志里也可能留下痕迹。
- 别在响应头里暴露
X-Download-Path这类自定义头 - 别用
Response.Redirect跳转到真实文件地址 - 静态文件目录必须从 IIS/Apache 配置层禁用目录浏览,但光靠这个不够
用 FileStreamResult 绕过路径暴露(ASP.NET MVC)
这是最干净的做法:控制器不暴露任何路径字符串,只传文件内容和元信息。框架内部用流读取,响应体里只有数据,URL 始终是你的路由地址(如 /download?id=123)。
-
FileStreamResult接收Stream和ContentType,不碰文件路径变量 - 实际读取用
File.OpenRead(filePath),但filePath只在服务端解析,不出现在 HTTP 流程中 - 记得手动设
Content-Disposition头,否则浏览器可能内嵌显示而非下载:Response.Headers["Content-Disposition"] = "attachment; filename=\"report.pdf\"";
Web API 中用 HttpResponseMessage 返回流(ASP.NET Core)
Core 里没 FileStreamResult,得靠 HttpResponseMessage + StreamContent。关键点在于:流必须支持随机访问(CanSeek == true),否则部分客户端(尤其是移动端)会失败或卡住。
- 别用
new MemoryStream(File.ReadAllBytes(filePath))——大文件直接 OOM - 正确做法是
new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 8192, useAsync: true) - 设置
Content-Length头能提升下载稳定性,可用new FileInfo(filePath).Length获取 - 如果文件路径来自用户输入,务必用
Path.GetFullPath+Path.GetDirectoryName校验是否在白名单目录内,防 ../ 目录穿越
为什么 Response.BinaryWrite 容易出错
老项目常见写法,但隐患多:它把字节数组全载入内存再发,大文件极易触发 OutOfMemoryException;且不自动处理分块传输(chunked encoding),IE 旧版本可能截断。
- 别用
File.ReadAllBytes配合BinaryWrite—— 千万别 - 如果非要用,至少改用
FileStream读取 +Response.OutputStream.Write分段写,但还得手动设Content-Length和缓冲策略 - ASP.NET Core 已移除
Response.BinaryWrite,迁移时必须重写逻辑
真正难的不是“怎么返回文件”,而是确保每次读取都走受控路径、每个响应头都不泄露上下文、每种客户端都能正确识别下载意图。路径校验、流生命周期、Content-Disposition 的双引号转义——这些细节漏一个,就可能让隐藏地址变成摆设。

















