You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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("错误:上传失败,请稍后重试");
        }
    }
}
最后几个小提醒
  1. 记得给上传目录配置合适的读写权限,避免权限问题导致文件保存失败;
  2. 生产环境中建议给文件名添加随机前缀,避免同名文件覆盖;
  3. 可以把大小、允许类型等参数放到配置文件(如.properties)中,方便后续调整。

内容的提问来源于stack exchange,提问作者user8910784

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:49:31