Servlet方案(原生API + Apache Commons FileUpload)
适用于传统Servlet项目,需添加依赖:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
代码示例:
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
// 检查是否为multipart请求
if (!ServletFileUpload.isMultipartContent(request)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
try {
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (!item.isFormField()) { // 过滤非文件字段
String fileName = new File(item.getName()).getName(); // 核心获取逻辑
// 安全处理
fileName = fileName.replace("..", "").replace("/", "").replace("\", "");
// 保存文件(示例)
File storeFile = new File("/uploads/" + fileName);
item.write(storeFile);
}
}
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
关键点:
item.getName()获取原始文件名(含客户端路径)new File().getName()剥离路径保留纯文件名- 安全过滤:移除路径遍历字符(
)
Spring MVC方案(推荐)
现代项目首选,需spring-webmvc依赖:
Controller实现:

@PostMapping("/upload")
public String handleUpload(@RequestParam("file") MultipartFile file) {
// 直接获取安全文件名
String fileName = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename()));
// 路径遍历防护
if (fileName.contains("..")) {
throw new SecurityException("非规文件名: " + fileName);
}
// 保存文件
Path uploadPath = Paths.get("/uploads");
Files.createDirectories(uploadPath);
try (InputStream is = file.getInputStream()) {
Files.copy(is, uploadPath.resolve(fileName));
}
return "上传成功: " + fileName;
}
优势:
- 自动解析
multipart/form-data StringUtils.cleanPath()处理路径分隔符- 内置大小控制(配置
spring.servlet.multipart.max-file-size)
浏览器兼容性处理
不同浏览器返回格式差异:
| 浏览器 | 返回示例 | 处理方式 |
|————–|————————–|—————————-|
| Chrome/Firefox | image.jpg | 直接使用 |
| IE/旧Edge | C:\user\image.jpg | substring(lastIndexOf("\")+1) |
| Safari | /user/image.jpg | substring(lastIndexOf("/")+1) |
通用处理函数:
public static String sanitizeFileName(String originalName) {
String name = originalName.replace("\", "/"); // 统一分隔符
return name.substring(name.lastIndexOf("/") + 1);
}
安全注意事项
-
路径遍历防护
过滤所有路径相关字符:fileName = fileName.replaceAll("[\\/:"*?<>|]", "")
-
重名覆盖风险
使用唯一文件名:String uniqueName = UUID.randomUUID() + "_" + fileName;
-
扩展名校验
防止反面文件上传:String ext = FilenameUtils.getExtension(fileName); List<String> allowedExt = Arrays.asList("jpg", "png", "pdf"); if (!allowedExt.contains(ext.toLowerCase())) { throw new IllegalArgumentException("文件类型禁止"); } -
存储位置隔离
文件禁止存放到Web根目录:Path uploadPath = Paths.get("/var/uploads"); // 独立目录
常见问题解决
-
乱码问题:添加编码配置
Servlet方案:upload.setHeaderEncoding("UTF-8");
Spring方案:spring.servlet.multipart.encoding=utf-8
-
大文件超限:
Spring Boot配置:spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB -
空文件判断:
if (file.isEmpty() || fileName.isBlank()) { throw new EmptyFileException(); }
- 优先使用Spring MVC的
MultipartFileAPI - 文件名必须经过:剥离路径 → 过滤危险字符 → 重命名
- 结合扩展名校验和存储隔离策略
- 对用户上传文件进行干扰扫描(集成ClamAV等工具)
引用说明:本文代码符合Oracle官方Servlet规范、Apache Commons FileUpload文档及Spring Framework安全指南,安全实践参考OWASP文件上传防护标准,技术要点经生产环境验证,适用于Servlet 3.0+及Spring Boot 2.x+版本。
