为了安全,有时我们需要限制前端上传文件的类型,这个功能可以结合Spring的拦截器和Hutool的文件类型判断来完成。
我们实现如下功能:
- 整个项目默认仅允许一些常见文件类型的上传,比如xlsx等
- 如果某个接口有具体要求,还可以在接口级指定该接口额外允许上传的文件类型
首先,我们需要加载hutool包:
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><!-- 版本号去官网找最新的就行 --><version>5.8.27</version></dependency>
下面是代码,实现了上面的功能:
java">
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AdditionAllowUploadFileTypes {String[] value() default "";
}
java">
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;/*** 默认支持上传:* 1. 文档类: "xlsx", "xls", "docx", "doc", "pdf", "txt"* 2. 图片/视频类: "jpg", "jpeg", "png", "bmp", "mp4"* 3. 压缩包:zip** @author */
@ConfigurationProperties(prefix = "myservice.filetype")
@Data
public class FileTypeProperties {private static final Logger LOGGER = LoggerFactory.getLogger(FileTypeProperties.class);/*** 默认允许的文件类型** @see cn.hutool.core.io.FileTypeUtil*/private List<String> defaultAllowTypes = Lists.newArrayList("xlsx", "xls", "docx", "doc", "pdf", "txt","jpg", "jpeg", "png", "bmp", "mp4","zip");/*** 自定义支持的扩展类型** @see cn.hutool.core.io.FileTypeUtil*/private List<String> additionalAllowTypes;private Set<String> allowedTypes;@PostConstructpublic void preSetAllowedTypes() {allowedTypes = new HashSet<>();if (CollectionUtil.isNotEmpty(defaultAllowTypes)) {for (String type : defaultAllowTypes) {if (StrUtil.isNotBlank(type)) {allowedTypes.add(type.toLowerCase(Locale.ROOT));}}}if (CollectionUtil.isNotEmpty(additionalAllowTypes)) {for (String type : additionalAllowTypes) {if (StrUtil.isNotBlank(type)) {allowedTypes.add(type.toLowerCase(Locale.ROOT));}}}LOGGER.info("File Allowed Types 初始化完成!");}
}
java">
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpStatus;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;/*** 限制上传文件类型** @author */
public class FileTypeFilter implements HandlerInterceptor {private final FileTypeProperties fileTypeProperties;public FileTypeFilter(FileTypeProperties fileTypeProperties) {this.fileTypeProperties = fileTypeProperties;}@Overridepublic boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) throws Exception {if (!(request instanceof MultipartHttpServletRequest)) {return true;}Map<String, MultipartFile> allFiles = ((MultipartHttpServletRequest) request).getFileMap();List<String> notAllowedFiles = getNotAllowedFiles(handler, allFiles);if (CollUtil.isNotEmpty(notAllowedFiles)) {String message = CharSequenceUtil.format("不被允许的文件类型!不被允许的文件列表如下:{}", notAllowedFiles);ServletUtil.write(response, message, MediaType.TEXT_PLAIN_VALUE));return false;}return true;}private List<String> getNotAllowedFiles(Object handler, Map<String, MultipartFile> allFiles) throws IOException {List<String> notAllowedFilenames = null;for (MultipartFile file : allFiles.values()) {if (!isAllowedFile(file, handler)) {if (notAllowedFilenames == null) {notAllowedFilenames = new ArrayList<>();}notAllowedFilenames.add(file.getOriginalFilename());}}return notAllowedFilenames;}boolean isAllowedFile(MultipartFile file, Object handler) throws IOException {String fileName = file.getOriginalFilename();try (InputStream in = file.getInputStream()) {String type = FileTypeUtil.getType(in, fileName);if (StringUtils.isBlank(type)) {// 无法通过文件头或者扩展名识别文件类型。return false;}String extType = FileUtil.extName(fileName);if (StringUtils.isBlank(extType)) {// 后缀读不到类型,直接拒绝return false;}// 文件头类型和后缀类型都要满足白名单if (CollectionUtils.containsAll(fileTypeProperties.getAllowedTypes(),Lists.newArrayList(type.toLowerCase(Locale.ROOT), extType.toLowerCase(Locale.ROOT)))) {return true;}String[] additionAllowUploadFileTypes = getAdditionAllowUploadFileTypes(handler);return Arrays.stream(additionAllowUploadFileTypes).anyMatch(type::equalsIgnoreCase) &&Arrays.stream(additionAllowUploadFileTypes).anyMatch(extType::equalsIgnoreCase);}}private String[] getAdditionAllowUploadFileTypes(Object handle) {String[] res = new String[0];if (!(handle instanceof HandlerMethod)) {return res;}HandlerMethod handlerMethod = (HandlerMethod) handle;if (handlerMethod.hasMethodAnnotation(AdditionAllowUploadFileTypes.class)) {AdditionAllowUploadFileTypes types = handlerMethod.getMethodAnnotation(AdditionAllowUploadFileTypes.class);if (types != null) {String[] allowedTypes = types.value();if (allowedTypes != null) {res = allowedTypes;}}}return res;}
}
使用方法:
项目中可以在application.yaml中指定项目级别额外允许的文件类型,默认允许的类型:"xlsx", "xls", "docx", "doc", "pdf", "txt", "jpg", "jpeg", "png", "bmp", "mp4","zip"
,可以通过myservice.filetype.default-allow-types
变量来覆盖
myservice:filetype: additional-allow-types: - xxx
接口级的可以在接口上使用注解@AdditionAllowUploadFileTypes("xxx")
来额外允许指定类型的文件上传。