SprngBoot整合tika做文件类型检测

文章介绍了ApacheTika在文件上传接口安全中的应用,包括内容检测、元数据提取,以及如何通过Tika防止恶意文件上传,同时提供了Tika的集成与使用示例。
摘要由CSDN通过智能技术生成


最近开发的一个项目需要将上传接口放开,做了很多安全性的工作,这个文件类型检测是其中的一环,趁着最近有空,就记录一下。

1.免登陆上传接口安全性风险和建议

免登录上传接口面临各种风险:恶意文件上传、DoS攻击、数据泄露、文件名注入攻击、隐藏文件上传…等。对应的防御策略如过滤和限制文件类型、限制文件大小、避免上传文件被执行、重命名文件、服务器端做入侵检测、上传文件病毒扫描…等,系统的安全需要多方面考虑,并不断优化和完善才行,今天主要介绍tika的使用,就不拓展更多了,有兴趣的朋友可以自行去了解,后面有空可能也会写一写。

2.Apache Tika 介绍

Apache Tika 是一个开源的内容检测和分析框架,由Apache软件基金会开发和维护的顶级项目。它可以从各种格式的文件中提取元数据和文本内容。Tika非常适合处理全文搜索、内容分析、翻译、内容提取等需要大量处理和分析文档内容的任务。

2.1 主要功能

Apache Tika的主要功能:

  1. 内容检测:通过检查文件内容或文件扩展名,Tika能够准确地判断文件的媒体类型(MIME类型)。
  2. 元数据提取:Tika能够从各种媒体类型的文件中提取元数据,比如标题、作者、时间戳等。
  3. 内容提取:Tika能够从文件中提取出文本、图片等内容。
  4. 语言检测:Tika可以检测文本内容的语言。
  5. 格式转换:Tika可以将各种格式的文件转换为XHTML内容。

2.2 主要项目

  1. tika-core: 这是Tika的核心库,提供了Tika API的主要接口和类,例如内容检测、元数据提取、内容提取等。此外,它还提供了一些工具类来处理MIME类型和字符集等。
  2. tika-parsers: 这个库包含了Tika可以使用的所有解析器。每一个解析器都是针对一种特定的文件格式(如PDF,Word,Excel,PowerPoint,HTML,XML,RTF,EPUB等)。这些解析器依赖于其他一些专门用于处理这些格式的库,如PDFBox,POI等。
  3. tika-app: 这是一个命令行应用程序,集成了tika-core和tika-parsers的功能,你可以使用它来在命令行中直接处理文件。
  4. tika-server: 这是一个RESTful服务,提供了Tika的所有功能。你可以在网络上任何地方使用它来处理文件。
  5. tika-bundle: 这是一个包含了tika-core和tika-parsers的大型jar包,方便你在项目中直接使用Tika的所有功能。
  6. tika-langdetect: 这个库提供了一种方法来检测文本内容的语言。
  7. tika-translate: 这个库提供了一种方法来翻译文本内容。
  8. **tika-dl: **这个库提供了一种方法来使用深度学习模型处理文件。

Tika的项目彼此之间有着紧密的关系。tika-core是所有其他项目的基础,tika-parsers为core提供了具体的解析功能,app、server和bundle等则是提供了不同形式的使用接口。其余的一些项目,如langdetect、translate和dl,则提供了更加特化的功能。

3.整合tika

3.1 pomxml引入依赖

<!-- https://mvnrepository.com/artifact/org.apache.tika/tika-core -->
<!-- 其他版本可自行去maven仓库查找 -->
<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-core</artifactId>
    <version>2.8.0</version>
</dependency>

3.2 tika的使用

3.2.1 第一版demo代码

public static void main(String[] args) {
    File file = new File("E:\\software\\ab.png");

    // 使用Tika检测文件类型
    MediaType mediaType;
    String mediaTypeExtension;
    String extName = getFileExtension(file.getName());
    try (TikaInputStream tis = TikaInputStream.get(file)) {
        mediaType = TikaConfig.getDefaultConfig().getDetector().detect(tis, new Metadata());
        mediaTypeExtension = MimeTypes.getDefaultMimeTypes().forName(mediaType.toString()).getExtension();
        mediaTypeExtension = mediaTypeExtension.replace(".", "");
    } catch (IOException | MimeTypeException e) {
        throw new RuntimeException("Could not read file", e);
    }
    if (!mediaTypeExtension.equals(extName)) {
        log.info("不匹配");
    } else {
        log.info("匹配");
    }
    log.info("MediaType>>>>{}",mediaType);
    log.info("type>>>>{}",mediaType.getType());
    log.info("mediaTypeExtension>>>>{}",mediaTypeExtension);
    log.info("extName>>>>{}",extName);
}

private static String getFileExtension(String fileName) {
int dotIndex = fileName.lastIndexOf(".");
if (dotIndex == -1) {
throw new CheckedException("文件名无扩展名,无法确定文件类型");
}
// 不包含点,所以使用dotIndex + 1
return fileName.substring(dotIndex + 1);
}

运行结果:
图片.png这里我使用了lombok的注解@Slf4j打印日志,没有引入的直接是java自带的 打印即可,这里的判断是正确的,现在我们直接修改文件的后缀再测试,ab.png修改为ab.gif:
图片.png
运行结果如下:图片.png
校验结果也是正确的,避免了不必要的类型上传到服务器。
但是这里要注意,tika会将魔数值相同的后缀类型标准化,比如jpeg,jif等会被标准化成jpg,修改代码测试:
图片.png
运行结果:
图片.png我们可以追溯一下代码:
图片.png图片.png第一个是框架自带的类型映射,第二个是自定义的类型映射,这个我还没自己动手弄过,因为官方提供的已经够使用了,然后我们打开tika-mimetypes.xml看一下:
图片.png而直接获取getExtension的方法如下
图片.png所以我们需要调整一下代码,我们看到这个类里还有一个getExtensions方法,获取这一个映射下的所有类型
图片.png修改代码:
图片.png图片.png运行结果:
图片.png

3.2.2 最终版完整代码

@Slf4j
@UtilityClass
public class FileDetectUtils {

    /**
     * 文件大小
     */
    private static final long MAX_FILE_SIZE = 10485760L;
    private static final List<String> MEDIA_TYPE_LIST = Lists.newArrayList("image","video");
    private static final Detector DETECTOR = TikaConfig.getDefaultConfig().getDetector();

    /**
     * 文件检测
     * @param file
     * @return
     */
    public void detect(MultipartFile file) {
        // 检查文件大小
        if (file.getSize() > MAX_FILE_SIZE) {
            throw new CheckedException("上传文件过大,请上传低于" + MAX_FILE_SIZE + "文件");
        }
        // 检查文件名 以.开头的文件不支持上传
        isValidFileName(file.getOriginalFilename());
        String extension = getFileExtension(file.getOriginalFilename());

        // 使用Tika检测文件类型
        MediaType mediaType;
        List<String> mediaTypeExtensions;
        try (TikaInputStream tis = TikaInputStream.get(file.getBytes())) {
            mediaType = DETECTOR.detect(tis, new Metadata());
            if (!MEDIA_TYPE_LIST.contains(mediaType.getType())) {
                log.error("不支持的文件类型,type:{}", mediaType.getType());
                throw new CheckedException("不支持的文件类型");
            }
            mediaTypeExtensions = MimeTypes.getDefaultMimeTypes().forName(mediaType.toString()).getExtensions();
        } catch (IOException e) {
            log.error("Could not read file", e);
            throw new CheckedException("请上传正确的文件类型");
        } catch (MimeTypeException e) {
            log.error("Could not get extension for media type", e);
            throw new CheckedException("请上传正确的文件类型");
        }
        // 验证文件类型
        if (!mediaTypeExtensions.contains(extension.toLowerCase())) {
            throw new CheckedException("文件类型已被修改,不支持上传");
        }

        // 如果需要,你可以在这里添加更深入的文件内容检查
        // 例如,对于PDF文件,你可能希望检查是否包含JavaScript或宏
    }

    /**
     * 校验文件名
     * @param fileName
     */
    private void isValidFileName(String fileName) {
        if (null == fileName) {
            throw new CheckedException("文件名不能为空");
        }
        // 简单的文件名检查,需要根据需求进行修改
        if (fileName.startsWith(StrUtil.DOT)) {
            throw new CheckedException("不支持以.开头的文件");
        }
    }

    /**
     * 因为tika解析出的文件后缀带了.   所以此处不去除
     * @param fileName
     * @return
     */
    private static String getFileExtension(String fileName) {
        int dotIndex = fileName.lastIndexOf(".");
        if (dotIndex == -1) {
            throw new CheckedException("文件名无扩展名,无法确定文件类型");
        }
        // 包含点,所以使用dotIndex
        return fileName.substring(dotIndex);
    }
}

a.我在其他地方做了文件重命名,如果你有需要,也可以使用UUID等工具重命名。
b.我这里限定两个主类型,image和video,你也可以使用子类型来限定,调整下代码即可

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值