介绍 项目中常会遇到需要文件上传和下载的地方,其中文件上传的安全问题不可忽视,下面是我考虑到的文件上传安全问题和用编码实现的过程
对文件大小的判断,防止恶意上传大文件挤占服务器资源
对文件类型的判断,这里实现的是对图片的判断
对图片进行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 90 91 92 93 94 95 96 97 <?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 51 52 53 54 55 @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 33 34 35 36 package com.dev.util;import org.springframework.web.multipart.MultipartFile;public class FileUtils { 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 16 17 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 55 56 57 58 59 60 @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项目地址