java-springboot图片上传校验之只允许上传png、jpg、jpeg这三种类型,且文件大小不能超过10M,且检查不能是脚本或者有害文件或可行性文件

方案优势

  1. 四重安全校验

    • 文件扩展名检查
    • MIME类型检测(使用Apache Tika)
    • 文件头特征验证
    • 可执行文件特征检测
  2. 两种集成方式

    • AOP切面:自动拦截所有包含MultipartFile参数的方法
    • 手动调用:灵活控制校验时机
  3. 防御措施

    • 使用try-with-resources确保流关闭
    • 精确的文件头特征检查(防御伪造文件)
    • 统一的异常处理机制

需要引入依赖

 

<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-core</artifactId>
    <version>2.7.0</version>
</dependency>
import org.apache.tika.Tika;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class ImageFileValidator {
    
    // 允许的MIME类型(使用Set提升查询性能)
    private static final Set<String> ALLOWED_MIME_TYPES = new HashSet<>(Arrays.asList(
        "image/png",
        "image/jpeg",
        "image/jpg"
    ));
    
    // 允许的文件扩展名
    private static final Set<String> ALLOWED_EXTENSIONS = new HashSet<>(Arrays.asList(
        "png", "jpg", "jpeg"
    ));
    
    // 最大文件大小(10MB)
    private static final long MAX_FILE_SIZE = 10 * 1024 * 1024;
    
    // 文件头特征检查(PNG/JPG/JPEG)
    private static final byte[][] IMAGE_MAGIC_NUMBERS = {
        {(byte) 0x89, 0x50, 0x4E, 0x47}, // PNG
        {(byte) 0xFF, (byte) 0xD8, (byte) 0xFF}, // JPEG/JPG
    };

    /**
     * 通用图片文件验证
     * @param file 上传的文件
     * @throws IOException 文件读取异常
     * @throws IllegalArgumentException 校验失败时抛出
     */
    public static void validateImageFile(MultipartFile file) throws IOException, IllegalArgumentException {
        // 基础检查
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("文件不能为空");
        }

        // 检查文件大小
        if (file.getSize() > MAX_FILE_SIZE) {
            throw new IllegalArgumentException("文件大小不能超过10MB");
        }

        // 检查文件扩展名
        String originalFilename = file.getOriginalFilename();
        if (originalFilename == null) {
            throw new IllegalArgumentException("文件名无效");
        }
        
        String fileExtension = getFileExtension(originalFilename).toLowerCase();
        if (!ALLOWED_EXTENSIONS.contains(fileExtension)) {
            throw new IllegalArgumentException("仅支持PNG、JPG、JPEG格式文件");
        }

        // 使用Tika检测真实MIME类型(防御伪造扩展名)
        try (InputStream is = file.getInputStream()) {
            String detectedMimeType = new Tika().detect(is);
            if (!ALLOWED_MIME_TYPES.contains(detectedMimeType.toLowerCase())) {
                throw new IllegalArgumentException("非法的文件类型");
            }

            // 检查文件头特征
            if (!isValidImageHeader(is)) {
                throw new IllegalArgumentException("非法的图片文件格式");
            }

            // 恶意文件检测
            checkForExecutableContent(is);
        }
    }

    /**
     * 获取文件扩展名
     */
    private static String getFileExtension(String filename) {
        int lastDot = filename.lastIndexOf(".");
        return lastDot == -1 ? "" : filename.substring(lastDot + 1);
    }

    /**
     * 验证文件头是否符合图片格式
     */
    private static boolean isValidImageHeader(InputStream is) throws IOException {
        is.mark(10); // 标记以便重置
        byte[] header = new byte[4];
        if (is.read(header) != header.length) {
            return false;
        }
        is.reset();

        // 检查PNG头
        if (Arrays.equals(Arrays.copyOf(header, 4), IMAGE_MAGIC_NUMBERS[0])) {
            return true;
        }
        
        // 检查JPEG头
        return Arrays.equals(Arrays.copyOf(header, 3), IMAGE_MAGIC_NUMBERS[1]);
    }

    /**
     * 检查可执行文件特征
     */
    private static void checkForExecutableContent(InputStream is) throws IOException {
        is.mark(1024);
        byte[] buffer = new byte[1024];
        int bytesRead = is.read(buffer);
        is.reset();

        if (bytesRead > 0) {
            // PE文件头检查(Windows可执行文件)
            if (bytesRead > 60 && buffer[0] == 0x4D && buffer[1] == 0x5A) {
                throw new IllegalArgumentException("检测到潜在有害文件内容");
            }
            
            // ELF文件头检查(Linux可执行文件)
            if (bytesRead > 4 && buffer[0] == 0x7F && buffer[1] == 0x45 && 
                buffer[2] == 0x4C && buffer[3] == 0x46) {
                throw new IllegalArgumentException("检测到潜在有害文件内容");
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值