File upload in Spring Upload: upload, check and handle exceptions

I would like to be able to upload images to the server, handle errors and exceptions gracefully, with error messages displayed to the user in the form, and, ideally, only using the existing barebones Spring Boot and Thymeleaf.

Using the gs-uploading-files sample project I can upload files to the server using Spring Boot and Thymeleaf. In application.properties, I set spring.http.multipart.max-file-size=1MB and spring.http.multipart.max-request-size=1MB . However, some security and validation problems are not resolved when downloading files larger than 1 MB.

  • Any file can be uploaded. For example, an html file can be uploaded and thus hosted on a server. How can files be limited in type? Can they be verified on the page before sending the request? If I have several ways to upload images, how can I check all MultipartFiles?

  • Users may try to overlap large files that exceed the default limits of Spring and embedded Tomcat. This results in org.springframework.web.multipart.MultipartException not being handled by Spring. How can the file size be checked before trying to download? In case this bypasses, can any exceptions to download files detected by Spring give a nice error message?

  • The Spring error page is not used by default as a backup for all exceptions. The MultipartException function returns a Tomcat full-glass exception page (see Log 1).


I was looking to try and find and implement a set of solutions.

The step to fixing number 1 is to change the handleFileUpload check the type of content, reject files that don't work !file.getContentType().toLowerCase().startsWith("image") . Will it always work? Can an attacker get around this? And how can I check each MultipartFile to keep the need to remember this every time?

 @PostMapping("/") public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) throws MultipartException, IllegalStateException { if (file != null && file.getContentType() != null && !file.getContentType().toLowerCase().startsWith("image")) throw new MultipartException("not img"); storageService.store(file); redirectAttributes.addFlashAttribute("message", "You successfully uploaded " + file.getOriginalFilename() + "!"); return "redirect:/"; } 

Adding @ExceptionHandler does not work, it just does not get called.

 @ExceptionHandler({ SizeLimitExceededException.class, MultipartException.class, java.lang.IllegalStateException.class }) public ModelAndView handleError(HttpServletRequest req, Exception e) { // error("Request: " + req.getRequestURL() + " raised " + ex); ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.addObject("timestamp", new Date()); mav.addObject("error", e.getClass()); mav.addObject("message", e.getMessage()); mav.addObject("status", HttpStatus.INTERNAL_SERVER_ERROR); mav.setViewName("error"); return mav; } 

The number 3 can be resolved by the global exception handler for all exceptions. (explained in detail in this post ). However, I am concerned that it might cancel the controller-level handler.

 package hello; import java.util.Date; import javax.servlet.http.HttpServletRequest; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; @ControllerAdvice class GlobalDefaultExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { // If the exception is annotated with @ResponseStatus rethrow it and let // the framework handle it - like the OrderNotFoundException example // at the start of this post. // AnnotationUtils is a Spring Framework utility class. if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) throw e; // Otherwise setup and send the user to a default error-view. ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.addObject("timestamp", new Date()); mav.addObject("error", e.getClass()); mav.addObject("message", e.getMessage()); mav.addObject("status", HttpStatus.INTERNAL_SERVER_ERROR); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; } } 

I tried this answer , which handles the exception, but returns an error page. I would like to go back to the original page and display a good error message.


Magazine 1:

 HTTP Status 500 - Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (1292555) exceeds the configured maximum (1048576) type Exception report message Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (1292555) exceeds the configured maximum (1048576) description The server encountered an internal error that prevented it from fulfilling this request. exception org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (1292555) exceeds the configured maximum (1048576) org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982) org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) javax.servlet.http.HttpServlet.service(HttpServlet.java:648) org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) javax.servlet.http.HttpServlet.service(HttpServlet.java:729) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:89) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) root cause org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (1292555) exceeds the configured maximum (1048576) org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:111) org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:85) org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:76) org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1099) org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:932) org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) javax.servlet.http.HttpServlet.service(HttpServlet.java:648) org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) javax.servlet.http.HttpServlet.service(HttpServlet.java:729) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:89) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) root cause java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (1292555) exceeds the configured maximum (1048576) org.apache.catalina.connector.Request.parseParts(Request.java:2871) org.apache.catalina.connector.Request.parseParameters(Request.java:3176) org.apache.catalina.connector.Request.getParameter(Request.java:1110) org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:381) org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) root cause org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (1292555) exceeds the configured maximum (1048576) org.apache.tomcat.util.http.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:811) org.apache.tomcat.util.http.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:256) org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:280) org.apache.catalina.connector.Request.parseParts(Request.java:2801) org.apache.catalina.connector.Request.parseParameters(Request.java:3176) org.apache.catalina.connector.Request.getParameter(Request.java:1110) org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:381) org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:70) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) note The full stack trace of the root cause is available in the Apache Tomcat/8.5.5 logs. Apache Tomcat/8.5.5 

+5
source share
2 answers

Try adding the following value to your application.properties file:

 spring.http.multipart.max-file-size=256KB spring.http.multipart.max-request-size=256KB 

Source: https://spring.io/guides/gs/uploading-files/

+1
source

To answer the question of how to check file types, I created a special validator for this.

First create an annotation:

 import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = {ImageFileValidator.class}) public @interface ValidImage { String message() default "Invalid image file"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } 

Then create the validator itself:

 import org.springframework.web.multipart.MultipartFile; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class ImageFileValidator implements ConstraintValidator<ValidImage, MultipartFile> { @Override public void initialize(ValidImage constraintAnnotation) { } @Override public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext context) { boolean result = true; String contentType = multipartFile.getContentType(); if (!isSupportedContentType(contentType)) { context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate( "Only PNG or JPG images are allowed.") .addConstraintViolation(); result = false; } return result; } private boolean isSupportedContentType(String contentType) { return contentType.equals("image/png") || contentType.equals("image/jpg") || contentType.equals("image/jpeg"); } } 

Finally, apply the annotation:

 public class CreateUserParameters { @NotNull @ValidImage private MultipartFile image; ... } 

I tested this with Spring Boot 1.5.10 (also with Thymeleaf)

For the maximum file size, I would also like to see a solution that works with the "standard error mechanism" so that you can display the error like other field errors, and the user can correct his error.

0
source

Source: https://habr.com/ru/post/1259079/


All Articles