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

相关推荐
我好喜欢你~19 分钟前
C#---StopWatch类
开发语言·c#
桦说编程2 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
lifallen2 小时前
Java Stream sort算子实现:SortedOps
java·开发语言
IT毕设实战小研2 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
没有bug.的程序员2 小时前
JVM 总览与运行原理:深入Java虚拟机的核心引擎
java·jvm·python·虚拟机
甄超锋3 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
cui__OaO3 小时前
Linux软件编程--线程
linux·开发语言·线程·互斥锁·死锁·信号量·嵌入式学习
阿华的代码王国3 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Zyy~3 小时前
《设计模式》装饰模式
java·设计模式
A尘埃3 小时前
企业级Java项目和大模型结合场景(智能客服系统:电商、金融、政务、企业)
java·金融·政务·智能客服系统