Servlet与JSP中基于Apache Commons File Upload的图片上传服务器端验证
嘿,我之前也遇到过一模一样的问题!客户端用JS做的文件大小和类型校验确实能提前给用户友好反馈,但服务器端的校验才是真正的安全防线——毕竟客户端的校验很容易被绕过。下面我就一步步教你用Apache Commons File Upload实现对应的验证逻辑,包你一看就会!
1. 先确认依赖是否到位
首先得确保你的项目里已经引入了Apache Commons File Upload和Commons IO的依赖(后者是前者的必备依赖项),如果用Maven的话,在pom.xml里加这段:
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.5</version> <!-- 用最新稳定版即可 --> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.15.1</version> </dependency>
2. 文件大小验证:全局+单个文件双限制
Apache Commons File Upload支持两种大小限制,能覆盖不同场景:
- 全局总大小:所有上传文件加起来的最大容量
- 单个文件大小:每个文件的最大允许容量
直接看代码怎么实现:
// 初始化文件上传工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); // 设置临时文件存储位置(可选,默认是系统临时目录) factory.setRepository(new File(System.getProperty("java.io.tmpdir"))); // 创建上传处理器 ServletFileUpload upload = new ServletFileUpload(factory); // 设置全局总大小限制,比如10MB(单位是字节) upload.setSizeMax(10 * 1024 * 1024); // 设置单个文件的大小限制,比如5MB upload.setFileSizeMax(5 * 1024 * 1024); try { // 解析请求中的文件 List<FileItem> items = upload.parseRequest(request); // 后续处理逻辑... } catch (SizeLimitExceededException e) { // 全局总大小超出限制的处理 response.getWriter().write("上传文件总大小不能超过10MB"); } catch (FileSizeLimitExceededException e) { // 单个文件大小超出限制的处理 response.getWriter().write("单个文件大小不能超过5MB"); } catch (Exception e) { e.printStackTrace(); response.getWriter().write("上传失败,请稍后重试"); }
这里要注意精准捕获异常,不同异常对应不同的错误提示,用户体验会更好。
3. 文件类型验证:双重校验更安全
千万别只靠文件名后缀来验证类型——恶意用户很容易把exe文件改成.png后缀上传。最好结合MIME类型和文件名后缀做双重校验:
// 允许的文件MIME类型 Set<String> allowedMimeTypes = new HashSet<>(Arrays.asList( "image/png", "image/jpeg", "image/gif" )); // 允许的文件后缀(兼容jpeg和jpg两种写法) Set<String> allowedExtensions = new HashSet<>(Arrays.asList( "png", "jpg", "jpeg", "gif" )); for (FileItem item : items) { // 跳过表单中的非文件字段 if (!item.isFormField()) { // 获取文件名 String fileName = item.getName(); // 获取文件的MIME类型 String contentType = item.getContentType(); // 校验MIME类型 if (!allowedMimeTypes.contains(contentType)) { response.getWriter().write("只允许上传PNG、JPEG、GIF格式的图片"); return; } // 校验文件名后缀 String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); if (!allowedExtensions.contains(fileExtension)) { response.getWriter().write("只允许上传PNG、JPEG、GIF格式的图片"); return; } // 到这里说明类型合法,可以继续保存文件 // item.write(new File("你的存储路径/" + fileName)); } }
我当初做的时候也踩过只校验后缀的坑,后来加了MIME类型校验才更稳妥。如果想更严谨,还可以用Apache Tika库读取文件真实内容判断类型,但上面的双重校验已经能覆盖大部分业务场景了。
完整的Servlet实现示例
把上面的逻辑整合起来,一个完整的图片上传Servlet大概是这样的:
@WebServlet("/upload") public class ImageUploadServlet extends HttpServlet { private static final long serialVersionUID = 1L; // 配置参数可以抽成常量或配置文件,方便后续修改 private static final long MAX_TOTAL_SIZE = 10 * 1024 * 1024; // 10MB private static final long MAX_SINGLE_FILE_SIZE = 5 * 1024 * 1024; // 5MB private static final Set<String> ALLOWED_MIME_TYPES = new HashSet<>(Arrays.asList( "image/png", "image/jpeg", "image/gif" )); private static final Set<String> ALLOWED_EXTENSIONS = new HashSet<>(Arrays.asList( "png", "jpg", "jpeg", "gif" )); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); // 初始化上传工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); // 设置大小限制 upload.setSizeMax(MAX_TOTAL_SIZE); upload.setFileSizeMax(MAX_SINGLE_FILE_SIZE); try { List<FileItem> items = upload.parseRequest(request); for (FileItem item : items) { if (!item.isFormField()) { String fileName = item.getName(); String contentType = item.getContentType(); // 类型校验 if (!ALLOWED_MIME_TYPES.contains(contentType)) { response.getWriter().write("错误:只允许上传PNG、JPEG、GIF格式的图片"); return; } String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); if (!ALLOWED_EXTENSIONS.contains(fileExtension)) { response.getWriter().write("错误:只允许上传PNG、JPEG、GIF格式的图片"); return; } // 保存文件到指定路径 String savePath = getServletContext().getRealPath("/uploads"); File saveDir = new File(savePath); if (!saveDir.exists()) { saveDir.mkdirs(); } // 生产环境建议给文件名加随机前缀,避免重复覆盖 item.write(new File(saveDir, fileName)); response.getWriter().write("上传成功!"); } } } catch (SizeLimitExceededException e) { response.getWriter().write("错误:上传文件总大小不能超过10MB"); } catch (FileSizeLimitExceededException e) { response.getWriter().write("错误:单个文件大小不能超过5MB"); } catch (Exception e) { e.printStackTrace(); response.getWriter().write("错误:上传失败,请稍后重试"); } } }
最后几个小提醒
- 记得给上传目录配置合适的读写权限,避免权限问题导致文件保存失败;
- 生产环境中建议给文件名添加随机前缀,避免同名文件覆盖;
- 可以把大小、允许类型等参数放到配置文件(如
.properties)中,方便后续调整。
内容的提问来源于stack exchange,提问作者user8910784
相关产品推荐
相关产品推荐

