文件上传
注意:以下内容包含
- 完成minio文件上传功能
- 完成minio文件下载功能
- 解决文件名称后缀与真实文件后缀不符问题
- 过滤xxs攻击的文件
- 校验pdf中js属性xxs攻击
1.添加pom依赖
<!--springboot版本 2.3.12.RELEASE-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- iText依赖 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<!-- 获取真实文件后缀 -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>1.28.2</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-parsers</artifactId>
<version>1.28.2</version>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.7</version>
<exclusions>
<exclusion>
<artifactId>jackson-core</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>okhttp</artifactId>
<groupId>com.squareup.okhttp3</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 解决minio启动报错 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
</dependencies>
2.application.yml配置
#port
server:
port: 9911
#minio
minio:
endpoint: http://172.21.40.72:9001/ # minio服务地址
accessKey: lIGhSpx5U2N0nKMCPKz2 # 桶key
secretKey: q44dP2sTmLdbDWAI5mdCn06st5IOWN6nnUPOgtbF #桶value
bucket-fuyao-public: fuyao-public #公开桶
3.minio配置类
@Configuration
public class MinIoConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
4.工具类
@Slf4j
public class PdfUtils {
/*根据输入流判断该文件的真实文件类型*/
public static boolean containsJavaScript(InputStream inputStream) {
PdfReader reader = null;
try {
reader = new PdfReader(inputStream);
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
PdfDictionary pageDict = reader.getPageN(i);
PdfObject pdfObject = pageDict.get(PdfName.ANNOTS);
if (pdfObject != null && pdfObject.isArray()) {
PdfArray pdfArray = (PdfArray) pdfObject;
for (int j = 0; j < pdfArray.size(); j++) {
PdfDictionary annotDict = pdfArray.getAsDict(j);
if (annotDict != null) {
PdfObject action = annotDict.get(PdfName.A);
if (action != null && action.isDictionary()) {
PdfDictionary actionDict = (PdfDictionary) action;
if (PdfName.JAVASCRIPT.equals(actionDict.get(PdfName.S))) {
return true;
}
}
}
}
}
}
PdfDictionary catalog = reader.getCatalog();
PdfDictionary names = catalog.getAsDict(PdfName.NAMES);
if (names != null) {
PdfDictionary javascript = names.getAsDict(PdfName.JAVASCRIPT);
if (javascript != null) {
return true;
}
}
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
if (reader != null) {
reader.close();
}
}
return false;
}
}
5.实现类
@Slf4j
@Service
public class FileServiceImpl implements FileService {
@Resource
private MinioClient minioClient;
@Value("${minio.bucket-fuyao-public}")
private String bucketName;
@Value("${minio.endpoint}")
private String url;
private static final Set<String> IMAGE_FORMATS = new HashSet<>();
private static final Set<String> ZIP_FORMATS = new HashSet<>();
private static final Set<String> FILE_FORMATS = new HashSet<>();
private static final Set<String> VIDEO_FORMATS = new HashSet<>();
private static final String SUFFIX_PDF = "application/pdf";
static {
// 初始化图片格式集合
IMAGE_FORMATS.add("image/jpeg");
IMAGE_FORMATS.add("image/png");
IMAGE_FORMATS.add("image/gif");
IMAGE_FORMATS.add("image/bmp");
// 初始化压缩包格式集合
ZIP_FORMATS.add("application/zip");
ZIP_FORMATS.add("application/vnd.rar");
ZIP_FORMATS.add("application/x-7z-compressed");
ZIP_FORMATS.add("application/x-tar");
ZIP_FORMATS.add("application/gzip");
// 初始化其他文件格式集合
FILE_FORMATS.add("text/plain");
FILE_FORMATS.add("application/pdf");
FILE_FORMATS.add("application/msword");
FILE_FORMATS.add("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
FILE_FORMATS.add("application/vnd.ms-excel");
FILE_FORMATS.add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
//视频
VIDEO_FORMATS.add("video/mp4");
}
@Override
public R upload(MultipartFile file) {
try {
// 检查文件是否为空
long fileSize = file.getSize();
if (fileSize == 0) {
return R.error(Constant.FILE_IS_EMPTY);
}
// 获取文件的原始后缀
Tika tika = new Tika();
Metadata metadata = new Metadata();
String realFileType = tika.detect(file.getInputStream(), metadata);
String contentType = file.getContentType();
System.out.println(contentType);
if (!realFileType.contains(SUFFIX_VIDEO)) {
if (!realFileType.equals(fileType)) {
return Pick.error(Constant.FILE_TYPE_ERROR);
}
if (realFileType.equals(SUFFIX_PDF)) {
try (InputStream inputStream = file.getInputStream()) {
if (PdfUtils.containsJavaScript(inputStream)) {
return Pick.error(Constant.FILE_PDF_CONTAIN_JS);
}
}
}
}
// 是否支持文件格式
String folder = verifyFileFormat(contentType);
if (Objects.isNull(folder)) {
return R.error(Constant.FILE_FORMAT_ERROR);
}
// 是否是pdf 且包含js属性
if (SUFFIX_PDF.equals(contentType)) {
try (InputStream inputStream = file.getInputStream()) {
if (PdfUtils.containsJavaScript(inputStream)) {
return R.error(Constant.FILE_PDF_CONTAIN_JS);
}
}
}
String fileName = UUID.randomUUID().toString().replaceAll("-", "") + "-" + file.getOriginalFilename();
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(folder + fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
return R.data(url + bucketName + "/" + folder + fileName);
} catch (Exception e) {
log.error(Constant.FILE_UPLOAD_ERROR, e.getMessage());
return R.error(Constant.FILE_UPLOAD_ERROR);
}
}
@Override
public void download(String filename, HttpServletResponse response) {
GetObjectArgs objectArgs = GetObjectArgs.builder()
.bucket(bucketName)
.object(filename).build();
try (GetObjectResponse res = minioClient.getObject(objectArgs)) {
byte[] buf = new byte[1024];
int len;
try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
while ((len = res.read(buf)) != -1) {
os.write(buf, 0, len);
}
os.flush();
byte[] bytes = os.toByteArray();
response.setCharacterEncoding("utf-8");
// 设置强制下载不打开
try (ServletOutputStream stream = response.getOutputStream()) {
stream.write(bytes);
stream.flush();
}
}
} catch (Exception e) {
log.error(Constant.FILE_DOWNLOAD_ERROR, e);
throw new RuntimeException(Constant.FILE_DOWNLOAD_ERROR);
}
}
/*根据文件类型上传对应minio文件夹位置*/
private String verifyFileFormat(String contentType) {
if (IMAGE_FORMATS.contains(contentType)) {
return Constant.FILE_IMAGE;
}
if (ZIP_FORMATS.contains(contentType)) {
return Constant.FILE_ZIP;
}
if (FILE_FORMATS.contains(contentType)) {
return Constant.FILE_FILE;
}
if (VIDEO_FORMATS.contains(contentType)) {
return Constant.FILE_VIDEO;
}
return null;
}
}
6.测试
上传pdf中含有js属性的文件