Spring Boot 校验用户上传的图片文件
图片上传是现代应用中非常常见的一种功能,也是风险比较高的一个地方。恶意用户可能会上传一些病毒、木马。这些东西不仅严重威胁服务器的安全还浪费了带宽,磁盘等资源。所以,在图片上传的接口中,一定要对用户上传的文件进行严格的校验。
本文介绍了 2 种对图片文件进行验证的方法可供你参考。
一、文件后缀校验
通过文件后缀(也就是文件扩展名,通常用于表示文件的类型),进行文件类型校验这是最常见的做法。
图片文件的后缀类型有很多,常见的只有:jpg、jpeg、gif、png、webp。我们可以在配置或者代码中定义一个“允许上传的图片后缀”集合,用于校验用户上传的图片文件。
package cn.springdoc.demo.web.controller;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;@RestController
@RequestMapping("/upload")
public class UploadController {// 允许上传的图片类型的后缀集合static final Set<String> imageSuffix = Set.of("jpg", "jpeg", "gif", "png", "webp");@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)public ResponseEntity<String> upload (@RequestParam("file") MultipartFile file ) throws IllegalStateException, IOException{// 文件的原始名称String fileName = file.getOriginalFilename();if (fileName == null) {return ResponseEntity.badRequest().body("文件名称不能为空");}// 解析出文件后缀int index = fileName.lastIndexOf(".");if (index == -1) {return ResponseEntity.badRequest().body("文件后缀不能为空");}String suffix = fileName.substring(index + 1);if (!imageSuffix.contains(suffix.trim().toLowerCase())) {return ResponseEntity.badRequest().body("非法的文件类型");}// IO 到程序运行目录下的 public 目录,这是默认的静态资源目录Path dir = Paths.get(System.getProperty("user.dir"), "public");if (!Files.isDirectory(dir)) {// 创建目录Files.createDirectories(dir);}file.transferTo(dir.resolve(fileName));// 返回相对访问路径return ResponseEntity.ok("/" + fileName);}
}
如上,代码很简单。先是获取客户端文件的名称,再从名称获取到文件的后缀。确定是合法文件后再 IO 到本地磁盘。
二、使用 ImageIO 校验
由于文件的后缀是可编辑的,恶意用户可以把一个 exe 文件的后缀改为 jpg 再上传到服务器。于是这种情况下,上面的这种校验方式就会失效,恶意文件会被 IO 到磁盘。
在基于上面的方法进行校验后,我们可以先把文件 IO 到临时目录,再使用 ImageIO 类去加载图片文件,如果 Images 加载的文件不是图片,则会返回 null。
package cn.springdoc.demo.web.controller;import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Set;
import java.util.UUID;import javax.imageio.ImageIO;import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;@RestController
@RequestMapping("/upload")
public class UploadController {// 允许上传的图片类型的后缀集合static final Set<String> imageSuffix = Set.of("jpg", "jpeg", "gif", "png", "webp");@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file)throws IllegalStateException, IOException {// 文件的原始名称String fileName = file.getOriginalFilename();if (fileName == null) {return ResponseEntity.badRequest().body("文件名称不能为空");}// 解析出文件后缀int index = fileName.lastIndexOf(".");if (index == -1) {return ResponseEntity.badRequest().body("文件后缀不能为空");}String suffix = fileName.substring(index + 1);if (!imageSuffix.contains(suffix.trim().toLowerCase())) {return ResponseEntity.badRequest().body("非法的文件类型");}// 获取系统中的临时目录Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"));// 临时文件使用 UUID 随机命名Path tempFile = tempDir.resolve(Paths.get(UUID.randomUUID().toString()));// copy 到临时文件file.transferTo(tempFile);try {// 使用 ImageIO 读取文件if (ImageIO.read(tempFile.toFile()) == null) {return ResponseEntity.badRequest().body("非法的文件类型");}// 至此,这的确是一个图片资源文件// IO 到运行目录下的 public 目录Path dir = Paths.get(System.getProperty("user.dir"), "public");if (!Files.isDirectory(dir)) {// 创建目录Files.createDirectories(dir);}Files.copy(tempFile, dir.resolve(fileName));// 返回相对访问路径return ResponseEntity.ok("/" + fileName);} finally {// 始终删除临时文件Files.delete(tempFile);}}
}
这种方式更为严格,不但要校验文件后缀还要校验文件内容。弊端也显而易见,会耗费更多的资源!
1、ImageIO.read 方法
最后说一下 ImageIO.read 方法,它会从系统中已注册的 ImageReader 中选择一个 Reader 对图片进行解码,如果没有 Reader 能够解码文件,则返回 null。也就是说,如果上传的图片类型,在系统中没有对应的 ImageReader 也会被当做是“非法文件”。
例如:webp 类型的图片文件,ImageIO.read 就读取不了,因为 JDK 没有预置读取 webp 图片的 ImageReader。
要解决这个问题,可以添加一个 webp-imageio 依赖。
<!-- https://mvnrepository.com/artifact/org.sejda.imageio/webp-imageio -->
<dependency><groupId>org.sejda.imageio</groupId><artifactId>webp-imageio</artifactId><version>0.1.6</version>
</dependency>
webp-imageio 库提供了 webp 图片的 ImageReader 实现,并以 SPI 的形式注册到了系统中,不要写其他任何代码就可以成功读取。
相关文章:
Spring Boot 校验用户上传的图片文件
图片上传是现代应用中非常常见的一种功能,也是风险比较高的一个地方。恶意用户可能会上传一些病毒、木马。这些东西不仅严重威胁服务器的安全还浪费了带宽,磁盘等资源。所以,在图片上传的接口中,一定要对用户上传的文件进行严格的…...
【springboot配置项动态刷新】与【yaml文件转换为java对象】
文章目录 一,序言二,准备工作1. pom.xml引入组件2. 配置文件示例 三,自定义配置项动态刷新编码实现1. 定义自定义配置项对象2. 添加注解实现启动时自动注入3. 实现yml文件监听以及文件变化处理 四,yaml文件转换为java对象1. 无法使…...
JS移动端触屏事件
在我们PC端中有许多的事件,那我们在移动端有没有事件呢?让我为大家介绍一下移动端常用的事件,触屏事件 触屏事件 touch (也称触摸事件),Android 和IOS 都有 touch 对象代表一个触摸点。触摸点可能是一根手指,也可能是一…...
C语言——打印1000年到2000年之间的闰年
闰年: 1、能被4整除不能被100整除 2、能被400整除 #define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int year;for(year 1000; year < 2000; year){if((year%4 0) && (year%100!0) || (year%400 0)){printf("%d ",ye…...
【Linux】【驱动】设备树下的paltform总线
【Linux】【驱动】设备树下的paltform总线 1. 驱动程序的完整代码2. 使用到的相关函数3 使用到的指令3.2 设备上使用的指令 1. 驱动程序的完整代码 主要是展示了通过总线上挂载的方式来实现相关的数据读取 实质上就是几个of函数的调用。 /** Author: topeet* Description: 设…...
洛谷 NOIP 2023 模拟赛-汪了个汪-题解
简要题意 棋盘上有 n n n 行,第 i i i 行有 i i i 个格子。你要在格子填 1 ∼ n 1\sim n 1∼n,满足: 每行第一个数互不相同所有在行上相邻的两个数所组成的无序对互不相同每行的数互不相同 n ≤ 4000 n\le4000 n≤4000 题解 容易发现…...
洛谷 NOIP 2023 模拟赛 P9836 种树
洛谷 NOIP 2023 模拟赛 P9836 种树 文章目录 洛谷 NOIP 2023 模拟赛 P9836 种树题目大意思路code 题目大意 路边有 n n n 棵树,每棵树的 高度 均为正整数,记作 p 1 , p 2 … p n p_1, p_2 \dots p_n p1,p2…pn。 定义一棵树的 宽度 为它高度的…...
链表经典OJ题(链表回文结构,链表带环,链表的深拷贝)
目录 前言 1.反转一个单链表。 2. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。 3.链表的回文结构。 4.链表带环问题(*****) 4.1是否带环 4.2 入环的节点 5.随机链表的复制(链表的深拷贝) 前言…...
AD教程 (十三)常见CHIP封装的创建
AD教程 (十三)常见CHIP(贴片)封装的创建 PCB封装是电子设计图纸和实物之间的映射体,具有精准数据的要求,在实际设计中需要通过规格书获取创建封装的数据参数。 PCB封装和实物的大小一致。PCB封装是承载实物…...
从0到1实现一个前端监控系统(附源码)
目录 一、从0开始 二、上报数据方法 三、上报时机 四、性能数据收集上报 收集上报FP 收集上报FCP 收集上报LCP 收集上报DOMContentLoaded 收集上报onload数据 收集上报资源加载时间 收集上报接口请求时间 五、错误数据收集上报 收集上报资源加载错误 收集上报js错…...
第7章-使用统计方法进行变量有效性测试-7.2-方差分析
目录 7.2 方差分析 7.2.1 单因素方差分析 组内变异 组间变异 总变异 随机误差...
【MongoDB】索引 – 文本索引(用权重控制搜索结果)
一、准备工作 这里准备一些数据 db.books.drop();db.books.insert({_id: 1, name: "Java", alias: "java 入门", description: "入门图书" }); db.books.insert({_id: 2, name: "C", alias: "c", description: "C 入…...
Git 入门使用
一、Git 入门 1.1 Git简介 Git是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。Git是由Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。 Git是目前世界上最先进的分布式版本控制系统,没有之一&a…...
如何写好接口自动化测试脚本
谈到接口测试,大家关注更多的是哪个工具更优秀,更好用。但是很少人关注到接口测试用例的设计问题,也很少人会去写接口用例,都代码化了嘛,还写什么用例,是吧? 这样真的对么?我们是不…...
openEuler编译安装nmon性能监控工具及可视化分析工具
ln 介绍 nmon(short for Nigel’s Monitor)是一个性能分析工具,由蓝色巨人IBM开发,最早用于自家操作系统UNIX,AIX (Advanced Interactive eXecutive)。现在也能用在Linux上。它可以显示系统的…...
96 前缀树Trie
前缀树 题解1 STL题解2 参考官方 Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。 请你实现 Trie 类: …...
“第六十六天”
这个我记得是有更优解的,不过还是明天发吧,明天想一想,看看能不能想起来 #include<string.h> int main() {char a[201] { 0 };char b[201] { 0 };scanf("%s %s", a, b);int na strlen(a);int nb strlen(b);int i 0, j …...
MYSQL5.7和MYSQL8配置主从
1、创建专门主从的账号 #登录 mysql -u root -p #创建用户 我这里用户名为test5,注意这里的ip是从库服务器的ip CREATE USER test5192.168.1.20 IDENTIFIED WITH mysql_native_password BY xxxxx; #给主从复制账号授权 grant replication slave on *.* to test5192…...
springboot苍穹外卖实战:九、小程序微信登录代码开发+商品浏览
微信登录 application.yml和application-dev.yml application-dev.yml sky:wechat:appid: xxxsecret: xxxapplication.yml sky:wechat:appid: ${sky.wechat.appid}secret: ${sky.wechat.secret}配置为微信用户生成jwt令牌时使用的配置项: application.yml sky…...
【MySQL系列】 第二章 · SQL(下)
写在前面 Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误或不足之处,请多多指正࿰…...
终极指南:readme.so无障碍设计如何为所有开发者打造包容性体验
终极指南:readme.so无障碍设计如何为所有开发者打造包容性体验 【免费下载链接】readme.so An online drag-and-drop editor to easily build READMEs 项目地址: https://gitcode.com/gh_mirrors/re/readme.so readme.so作为一款在线拖拽式README编辑器&…...
Android UI性能优化终极指南:10个让应用更流畅的秘诀
Android UI性能优化终极指南:10个让应用更流畅的秘诀 【免费下载链接】user-interface-samples Multiple samples showing the best practices in the user interface on Android. 项目地址: https://gitcode.com/gh_mirrors/us/user-interface-samples 想要…...
科研党必备:Python脚本批量下载DOI文献的保姆级教程(附避坑指南)
科研党必备:Python脚本批量下载DOI文献的保姆级教程(附避坑指南) 文献检索与下载是科研工作中不可或缺的环节。对于需要处理大量文献的研究者来说,手动逐一下载不仅效率低下,还容易出错。本文将详细介绍如何使用Python…...
realme Q3 5G刷机全攻略:从TWRP到Magisk Root权限获取
1. realme Q3 5G刷机前的准备工作 在开始刷机之前,我们需要做好充分的准备工作。realme Q3 5G(型号RMX3161)作为一款性价比极高的5G手机,搭载高通骁龙750G处理器,确实是个不错的刷机选择。不过刷机有风险,操…...
WPF新手村教程(七)—— 终章(MVVM架构初见杀)姑
1. 哑铃图是什么? 哑铃图(Dumbbell Plot),有时也称为DNA图或杠铃图,是一种用于比较两个相关数据点的可视化图表。 它源于人们对更有效数据比较方式的持续探索。 在传统的时间序列比较中,我们通常使用两条折…...
Agent Client Protocol 全景解析涛
1. 核心概念 在 Antigravity 中,技能系统分为两层: Skills (全局库):实际的代码、脚本和指南,存储在系统级目录(如 ~/.gemini/antigravity/skills)。它们是“能力”的本体。 Workflows (项目级):…...
如何导出包含事件调度器(Events)的配置_数据库自动化任务的备份
mysqldump 默认不导出 EVENTS,必须显式加 --events;还需配合 --routines 和 --triggers 确保依赖逻辑完整,并注意 --skip-definer 和 --set-gtid-purgedOFF 等关键参数。mysqldump 默认不导出 EVENTS,必须显式加 --eventsmysql 的…...
终极指南:Vue-Multiselect 源码架构深度剖析与优秀组件设计模式解析
终极指南:Vue-Multiselect 源码架构深度剖析与优秀组件设计模式解析 【免费下载链接】vue-multiselect Universal select/multiselect/tagging component for Vue.js 项目地址: https://gitcode.com/gh_mirrors/vu/vue-multiselect Vue-Multiselect 是一个功…...
mPLUG视觉问答快速上手:5分钟完成本地部署,支持多格式图片+自然语言提问
mPLUG视觉问答快速上手:5分钟完成本地部署,支持多格式图片自然语言提问 你是不是经常遇到这种情况:看到一张复杂的图表,想快速知道它表达了什么;或者拿到一张产品设计图,想了解其中的细节信息;…...
沐曦股份曦云C系列GPU Day 0 适配智谱GLM-5.1 全栈技术领跑国产AI生态
4月8日,智谱新一代旗舰模型GLM-5.1实现开源。目前,沐曦股份曦云 C 系列 GPU已完成该系列模型Day 0 全量适配,再度以全栈自主技术实力,领跑国产 GPU 生态适配赛道。相比于GLM-5,GLM-5.1的整体能力得到了全面提升&#x…...
