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 是更好的选择。

相关推荐
雨中飘荡的记忆1 天前
ElasticJob分布式调度从入门到实战
java·后端
考虑考虑2 天前
JDK25模块导入声明
java·后端·java ee
_小马快跑_2 天前
Java 的 8 大基本数据类型:为何是不可或缺的设计?
java
Re_zero2 天前
线上日志被清空?这段仅10行的 IO 代码里竟然藏着3个毒瘤
java·后端
洋洋技术笔记2 天前
Spring Boot条件注解详解
java·spring boot
程序员清风2 天前
程序员兼职必看:靠谱软件外包平台挑选指南与避坑清单!
java·后端·面试
皮皮林5513 天前
利用闲置 Mac 从零部署 OpenClaw 教程 !
java
华仔啊3 天前
挖到了 1 个 Java 小特性:var,用完就回不去了
java·后端
SimonKing3 天前
SpringBoot整合秘笈:让Mybatis用上Calcite,实现统一SQL查询
java·后端·程序员
日月云棠3 天前
各版本JDK对比:JDK 25 特性详解
java