介绍
项目中常会遇到需要文件上传和下载的地方,其中文件上传的安全问题不可忽视,下面是我考虑到的文件上传安全问题和用编码实现的过程
对文件大小的判断,防止恶意上传大文件挤占服务器资源
对文件类型的判断,这里实现的是对图片的判断
对图片进行resize处理,防止图片嵌入恶意可执行的代码,通过压缩可以实现对嵌入可代码的破坏
文件保存的地址有两种,一个就是借助第三方服务器进行保存(七牛云),或者是放在自己的服务器,在成功读取文件后,进行保存的时候,可以对文件名进行修改,采用随机数,一定程度上提高了安全性
文件服务器和应用服务器的分开,避免对应用程序的直接破坏
文件夹权限的设置,对用户上传的文件夹设置只读权限,可以有效防止远端直接启动木马程序
当然,假如这个功能不需要,直接关闭文件上传功能是最安全的
编码
此Demo实现文件上传的读取保存,并在数据库中插入数据
项目目录

pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.5.RELEASE</version> <relativePath/> </parent> <groupId>com.dev</groupId> <artifactId>springboot-file-upload-download</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-file-upload-download</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.1</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.57</version> </dependency>
<dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.8</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
|
单文件上传的测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| /\*\* \* 单文件上传 \* @param file 前端传文件的参数 \* @return \*/ @RequestMapping("/upload") public String upload(@RequestParam("file") MultipartFile file,HttpServletRequest request) { FileDoc fileDoc = new FileDoc(); try { if (file.isEmpty()) { return "文件为空"; }
long size = file.getSize(); log.info("文件大小:" + size);
if (!FileUtils.checkFileSize(file, 20, "M")) { log.info("上传文件规定小于20MB"); return "上传文件规定小于20MB"; }
String filename = file.getOriginalFilename(); log.info("上传的文件名为:" + filename);
String suffixName = filename.substring(filename.lastIndexOf(".")); log.info("文件的后后缀名:" + suffixName);
fileDoc.setFile\_name(CodeGenerateUtil.generateVerCode(6).toString()+suffixName);
fileDoc.setIp\_addr(request.getRemoteAddr()); File dest = new File(FILEPATH+fileDoc.getFile\_name());
if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } file.transferTo(dest); int i = fileDocService.uploadFile(fileDoc); if (i > 0){ return "文件上传成功"; }else { return "文件上传失败"; } } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return "上传失败"; }
|
FileUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.dev.util; import org.springframework.web.multipart.MultipartFile; /\*\* \* @author 路飞 \* @create 2020/11/1 \*/ public class FileUtils { /\*\* \*检查文件大小 \* @param file \* @param size \* @param unit \* @return \*/ public static boolean checkFileSize(MultipartFile file,int size,String unit){ long len = file.getSize(); double fileSize = 0; if ("B".equals(unit.toUpperCase())){ fileSize = len; }else if ("k".equals(unit.toUpperCase())){ fileSize = (double)len / 1024; }else if ("M".equals(unit.toUpperCase())){ fileSize = (double)len / 1048576; }else if ("G".equals(unit.toUpperCase())){ fileSize = (double)len / 1073741824; } if (fileSize > size){ return false; } return true; } }
|
效果图:


测试超过上传要求的文件

测试多文件上传

在进行多文件上传的时候,会对每个文件进行校验,不符合上传要求的会直接返回

测试文件下载就不测试了,这里提供两种方法,第一种就是直接利用IO直接读取文件,进行数据传输,第二种就是直接利用springboot的资源映射配置类,把服务器的文件资源映射到互联网上,直接请求路径即可下载文件
WebMvcConfigurer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebMvcConfigurer extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/\*\*").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/image/\*\*").addResourceLocations("file:C:/image/"); registry.addResourceHandler("/lost/\*\*").addResourceLocations("file:C:/lost/"); } }
|
最后,是对图片上传的测试,这里引入谷歌的Thumbnailator,对图片进行压缩处理,破坏掉嵌入可执行代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| @RequestMapping("/uploadImage") public String upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) { FileDoc fileDoc = new FileDoc(); try { if (file.isEmpty()) { return "文件为空"; }
long size = file.getSize(); log.info("文件大小:" + size);
if (!FileUtils.checkFileSize(file, 10, "M")) { log.info("上传文件规定小于10MB"); return "上传文件规定小于10MB"; }
String filename = file.getOriginalFilename(); log.info("上传的文件名为:" + filename);
String suffixName = filename.substring(filename.lastIndexOf(".")); log.info("文件的后后缀名:" + suffixName);
if (!ImageTypeUtils.checkImageUtils(suffixName)){ return "请上传图片格式为jpg,png,gif的图片"; }
fileDoc.setFile\_name(CodeGenerateUtil.generateVerCode(6).toString()+suffixName);
fileDoc.setIp\_addr(request.getRemoteAddr()); File dest = new File(FILEPATH+fileDoc.getFile\_name());
if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } file.transferTo(dest);
Thumbnails.of(FILEPATH+fileDoc.getFile\_name()) .scale(1f) .outputQuality(0.7F) .toFile(FILEPATH+fileDoc.getFile\_name()); log.info("图片压缩成功"); int i = fileDocService.uploadFile(fileDoc); if (i > 0){ return "文件上传成功"; }else { return "文件上传失败"; } } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return "上传失败"; }
|
Thumbnails的使用方法
scale: 按照比例进行缩放。范围:0.0~N。
scale(0.5) 宽高比例都是50%缩放 , scale(1,0.5) 宽不变,高为50%缩放
1 2 3 4 5
| Thumbnails.of("原图文件的路径") .scale(1F) .outputQuality(0.7F) .watermark(Positions.BOTTOM\_RIGHT, ImageIO.read(水印), 0.5f) .toFile("转换后文件的路径");
|
格式的校验

成功上传后会对图片进行压缩

自己写的一个简单的Demo,未考虑的安全问题还有很多,对Java初学者应该会很有帮助。
详细代码见Github:
Github项目地址