Java后端文件类型检测(防伪造)

在 Spring Boot 项目中,为了防止用户伪造 Content-Type(例如将 .txt 文件改为 image/jpeg 上传),可以通过检查文件的 Magic Number (文件头签名)来验证文件的真实类型。以下是 详细实现步骤完整代码示例


1. 原理说明

  • Magic Number :文件头部的一组特定字节,用于标识文件类型(如 JPEG 的文件头是 FF D8 FF)。

  • 为什么需要MultipartFile.getContentType() 依赖客户端上传的 Content-Type 头,可以被篡改,而文件头是二进制数据,无法伪造。


2. 实现方案

方案一:手动解析文件头(轻量级)

适合简单场景,无需引入额外依赖。

方案二:使用 Apache Tika(推荐)

功能强大,支持 1000+ 文件类型的检测。


方案一:手动解析文件头

(1) 常见文件的 Magic Number
文件类型 文件头(Hex) ASCII 表示
JPEG FF D8 FF ÿØÿ
PNG 89 50 4E 47 0D 0A 1A 0A ‰PNG....
GIF 47 49 46 38 GIF8
(2) 实现工具类 FileHeaderValidator
java 复制代码
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class FileHeaderValidator {

    // 定义支持的文件类型及其Magic Number
    private static final Map<String, byte[]> FILE_TYPE_MAP = new HashMap<>();

    static {
        FILE_TYPE_MAP.put("image/jpeg", new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF});
        FILE_TYPE_MAP.put("image/png", new byte[]{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A});
    }

    /**
     * 通过文件头验证文件真实类型
     */
    public static boolean validateFileType(MultipartFile file, String expectedType) throws IOException {
        byte[] expectedHeader = FILE_TYPE_MAP.get(expectedType);
        if (expectedHeader == null) {
            throw new IllegalArgumentException("不支持的目标类型: " + expectedType);
        }

        try (InputStream is = file.getInputStream()) {
            byte[] fileHeader = new byte[expectedHeader.length];
            if (is.read(fileHeader) != fileHeader.length) {
                return false;
            }
            return Arrays.equals(fileHeader, expectedHeader);
        }
    }

    /**
     * 自动检测文件真实类型
     */
    public static String detectRealFileType(MultipartFile file) throws IOException {
        try (InputStream is = file.getInputStream()) {
            byte[] fileHeader = new byte[8]; // 读取前8字节(足够覆盖常见类型)
            if (is.read(fileHeader) != fileHeader.length) {
                return "unknown";
            }

            // 检查JPEG
            if (fileHeader[0] == (byte) 0xFF && fileHeader[1] == (byte) 0xD8 && fileHeader[2] == (byte) 0xFF) {
                return "image/jpeg";
            }

            // 检查PNG
            if (fileHeader[0] == 0x89 && fileHeader[1] == 0x50 && fileHeader[2] == 0x4E && 
                fileHeader[3] == 0x47 && fileHeader[4] == 0x0D && fileHeader[5] == 0x0A && 
                fileHeader[6] == 0x1A && fileHeader[7] == 0x0A) {
                return "image/png";
            }

            return "unknown";
        }
    }
}
(3) 在Controller中使用
java 复制代码
@PostMapping("/upload")
public ResponseEntity<String> uploadAvatar(@RequestParam("file") MultipartFile file) {
    try {
        // 1. 检查真实类型是否为JPEG或PNG
        String realType = FileHeaderValidator.detectRealFileType(file);
        if (!"image/jpeg".equals(realType) && !"image/png".equals(realType)) {
            return ResponseEntity.badRequest().body("文件真实类型不是JPEG/PNG");
        }

        // 2. 检查文件大小
        if (file.getSize() > 2 * 1024 * 1024) {
            return ResponseEntity.badRequest().body("文件大小不能超过2MB");
        }

        // 3. 保存文件
        file.transferTo(new File("/tmp/uploads/" + file.getOriginalFilename()));
        return ResponseEntity.ok("上传成功");

    } catch (IOException e) {
        return ResponseEntity.status(500).body("文件处理失败");
    }
}

方案二:使用 Apache Tika(推荐)

Apache Tika 是一个专业的文件内容检测工具,支持 1000+ 文件类型。

(1) 添加依赖
XML 复制代码
<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-core</artifactId>
    <version>2.9.0</version>
</dependency>
(2) 实现类型检测
java 复制代码
import org.apache.tika.Tika;
import java.io.IOException;

public class TikaFileValidator {

    private static final Tika tika = new Tika();

    /**
     * 检测文件的真实MIME类型
     */
    public static String detectRealFileType(MultipartFile file) throws IOException {
        return tika.detect(file.getInputStream());
    }
}
(3) 在Controller中使用
java 复制代码
@PostMapping("/upload")
public ResponseEntity<String> uploadAvatar(@RequestParam("file") MultipartFile file) {
    try {
        // 1. 使用Tika检测真实类型
        String realType = TikaFileValidator.detectRealFileType(file);
        if (!realType.equals("image/jpeg") && !realType.equals("image/png")) {
            return ResponseEntity.badRequest().body("仅支持JPEG/PNG格式");
        }

        // 2. 其他校验逻辑...
        return ResponseEntity.ok("上传成功");

    } catch (IOException e) {
        return ResponseEntity.status(500).body("文件检测失败");
    }
}

3. 对比两种方案

方案 优点 缺点 适用场景
手动解析文件头 无依赖、轻量级 需要维护Magic Number,扩展性差 仅需支持少量固定类型
Apache Tika 支持千种类型,自动更新类型库 引入额外依赖 需要高可靠性或复杂类型

4. 完整流程

  • 前端 :用户通过 <input type="file"> 选择文件。

  • 后端

    • 接收 MultipartFile

    • 通过文件头或 Tika 检测真实类型。

    • 校验类型、大小等。

    • 保存文件或返回错误。


5. 测试案例

java 复制代码
// 测试伪造的JPEG文件(实际是.txt)
MockMultipartFile fakeJpeg = new MockMultipartFile(
    "file", "test.jpg", "image/jpeg", "This is a text file".getBytes()
);

// 使用Tika检测会返回 "text/plain"
String realType = TikaFileValidator.detectRealFileType(fakeJpeg); 
assert realType.equals("text/plain");

6. 注意事项

  • 性能:文件头检测只需读取前几个字节,速度快;Tika 可能需要更多解析时间。

  • 安全性:即使通过验证,仍需防范恶意文件(如压缩炸弹),建议在保存前扫描。

  • 扩展性:如果需要支持更多类型(如PDF、GIF),Tika 是更好的选择。

相关推荐
用户8181870627464 分钟前
第27章 消息丢失/重复消费的全链路排查(生产者→Broker→消费者)
java·后端
索隆zoro19 分钟前
Army 对 jOOQ
java·后端
二炮手亮子27 分钟前
java责任链模式
java
529宝宝起名网39 分钟前
用 Python 开发历史名字查询与起名灵感工具:从古籍人物数据库到名字文化故事生成
开发语言·前端·python
AI深栈44 分钟前
第 10 章 · Embedding、VectorStore 与 RAG
java·人工智能
ocean21031 小时前
2025-2026年C++专题大厂面试高频问题
开发语言·c++·面试
Java内核笔记1 小时前
Spring Boot 4 SSRF 防护源码剖析:InetAddressFilter 挡住内网地址与云元数据
java·后端
白远山1 小时前
健身场馆无人自动化系统实战指南:从架构设计到部署全流程解析
java·开发语言·架构·需求分析
user_admin_god1 小时前
第 09 篇:实践一 —— 文本摘要(Map-Reduce 分块)
java·人工智能·spring boot·语言模型
咖啡八杯1 小时前
Controller 基类封装:BaseController 的模板方法设计
java·架构·设计