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

相关推荐
Jerry&Grj7 分钟前
SpringBoot埋点功能技术实现方案深度解析:架构设计、性能优化与扩展性实践
java·微服务·性能优化·springboot·架构设计·埋点技术
没有bug.的程序员13 分钟前
Redis Stream:轻量级消息队列深度解析
java·数据库·chrome·redis·消息队列
总有刁民想爱朕ha23 分钟前
车牌模拟生成器:Python3.8+Opencv代码实现与商业应用前景(C#、python 开发包SDK)
开发语言·python·数据挖掘
用户81607918333333 分钟前
告别“魔法”:包你解决 Gradle 的下载慢问题
java
小菜全1 小时前
uniapp新增页面及跳转配置方法
开发语言·前端·javascript·vue.js·前端框架
人衣aoa1 小时前
Python编程基础(八) | 类
开发语言·python
晚云与城1 小时前
今日分享:C++ Stack和queue(栈与队列)
开发语言·c++
当归10241 小时前
SQL Server死锁排查实战指南
java·服务器·网络
小莞尔1 小时前
【51单片机】【protues仿真】基于51单片机停车场的车位管理系统
c语言·开发语言·单片机·嵌入式硬件·51单片机
张烫麻辣亮。1 小时前
golang-gin包
开发语言·golang·gin