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

相关推荐
Ro Jace8 分钟前
计算机专业基础教材
java·开发语言
代码游侠24 分钟前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
mango_mangojuice26 分钟前
Linux学习笔记(make/Makefile)1.23
java·linux·前端·笔记·学习
程序员侠客行30 分钟前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
devmoon32 分钟前
运行时(Runtime)是什么?为什么 Polkadot 的 Runtime 可以被“像搭积木一样”定制
开发语言·区块链·智能合约·polkadot·runtmie
时艰.33 分钟前
Java 并发编程 — 并发容器 + CPU 缓存 + Disruptor
java·开发语言·缓存
丶小鱼丶38 分钟前
并发编程之【优雅地结束线程的执行】
java
市场部需要一个软件开发岗位43 分钟前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
忆~遂愿1 小时前
GE 引擎进阶:依赖图的原子性管理与异构算子协作调度
java·开发语言·人工智能
沐知全栈开发1 小时前
API 类别 - 交互
开发语言