Spring Boot 与前端文件下载问题:大文件、断点续传与安全校验
前言
在企业级 Spring Boot 项目中,文件下载 是非常常见的功能场景:
用户下载报表、合同、发票 PDF
下载图片、音视频资源
系统导出大规模 Excel/CSV 数据
然而,很多开发者在实现文件下载时,会遇到 下载失败、文件损坏、性能瓶颈、断点续传不生效 等问题。
本文将结合 Spring Boot 实践,详细解析 文件下载的常见问题与最佳实践,包括:
普通小文件下载
大文件下载与性能优化
断点续传(Range 支持)
文件下载的安全校验(防盗链、防越权)
一、Spring Boot 文件下载的常见问题
在实际开发中,文件下载可能会遇到以下问题:
文件名乱码:不同浏览器对
Content-Disposition
的解析差异导致大文件内存溢出:一次性读入内存,OOM 或性能崩溃
断点续传失败:未正确处理 HTTP Range 请求头
安全风险:直接拼接文件路径,可能被恶意访问系统敏感文件
盗链问题:外部网站直接引用下载接口,导致带宽浪费
二、小文件下载(基础实现)
Spring Boot 提供了非常简便的文件下载实现,直接使用 ResponseEntity
即可:
@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) throws IOException {Path path = Paths.get("files").resolve(fileName);Resource resource = new UrlResource(path.toUri());return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"").contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource);
}
✅ 适合小文件下载(几 MB 以内),但不适合大文件。
三、大文件下载与性能优化
如果文件较大(几十 MB 甚至几 GB),一次性加载到内存会非常危险,容易 OOM。 正确做法是 使用流式下载(Streaming):
@GetMapping("/download/stream/{fileName}")
public void downloadBigFile(@PathVariable String fileName, HttpServletResponse response) throws IOException {File file = new File("files", fileName);response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));OutputStream os = response.getOutputStream()) {byte[] buffer = new byte[1024 * 1024]; // 1MB 缓冲区int len;while ((len = bis.read(buffer)) != -1) {os.write(buffer, 0, len);os.flush();}}
}
✅ 优点:
避免一次性加载,降低内存占用
下载过程更加稳定
四、断点续传(支持 Range 请求头)
对于大文件,用户可能因网络中断而下载失败,重新下载会浪费带宽。 HTTP 协议支持 Range 请求头 来实现断点续传。
示例实现:
@GetMapping("/download/range/{fileName}")
public void downloadFileWithRange(@PathVariable String fileName,HttpServletRequest request,HttpServletResponse response) throws IOException {File file = new File("files", fileName);long fileLength = file.length();// 获取 Range 头String range = request.getHeader("Range");long start = 0, end = fileLength - 1;if (range != null && range.startsWith("bytes=")) {String[] parts = range.replace("bytes=", "").split("-");start = Long.parseLong(parts[0]);if (parts.length > 1) {end = Long.parseLong(parts[1]);}}long contentLength = end - start + 1;response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);response.setHeader("Accept-Ranges", "bytes");response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength);response.setHeader("Content-Length", String.valueOf(contentLength));response.setContentType("application/octet-stream");try (RandomAccessFile raf = new RandomAccessFile(file, "r");OutputStream os = response.getOutputStream()) {raf.seek(start);byte[] buffer = new byte[1024 * 1024];long remaining = contentLength;int len;while ((remaining > 0) && (len = raf.read(buffer, 0, (int) Math.min(buffer.length, remaining))) != -1) {os.write(buffer, 0, len);remaining -= len;}}
}
✅ 优点:支持断点续传(浏览器或下载工具可自动续传)。
五、文件下载的安全校验
文件下载接口是敏感的,如果未加保护,可能被恶意利用:
1. 越权访问问题
直接拼接路径:
/download/../../etc/passwd
可能导致泄露系统文件解决方法:严格限制文件路径,只允许访问白名单目录
String safeDir = "files";
Path targetPath = Paths.get(safeDir).resolve(fileName).normalize();
if (!targetPath.startsWith(Paths.get(safeDir))) {throw new SecurityException("非法文件路径!");
}
2. 鉴权校验
文件一般与用户权限绑定,例如:
合同文件只能由合同所属用户下载
报表文件只能由管理员或指定角色下载
实现:在下载接口中加入 Spring Security 或自定义权限校验。
@PreAuthorize("hasRole('ADMIN') or @fileService.canAccessFile(#fileName, authentication)")
@GetMapping("/download/secure/{fileName}")
public ResponseEntity<Resource> secureDownload(@PathVariable String fileName) {// 校验通过后下载
}
3. 防盗链(Referer 校验)
防止外部网站直接引用下载接口,可以校验请求头:
String referer = request.getHeader("Referer");
if (referer == null || !referer.startsWith("https://mydomain.com")) {throw new SecurityException("非法请求!");
}
六、最佳实践总结
小文件下载:
ResponseEntity<Resource>
即可大文件下载:使用 流式下载,避免内存溢出
断点续传:正确处理 Range 请求头,返回
206 Partial Content
安全性:
严格控制文件目录
鉴权校验(Spring Security / 自定义规则)
防盗链校验
七、结语
Spring Boot 文件下载并不是一行代码就能搞定的简单功能,而是需要根据 文件大小、用户体验、安全性要求 做不同优化。
如果是 小文件,直接返回即可
如果是 大文件,必须使用流式下载,避免 OOM
如果要 提升用户体验,断点续传必不可少
如果是 企业级系统,一定要加入鉴权和防盗链
在实际项目中,可以结合 Nginx 静态资源下载 + Spring Boot 鉴权签名 来进一步优化性能。