2016-11-01 43 views
5

Chciałbym móc przesyłać obrazy na serwer, z wdzięcznością obchodzić się z błędami i wyjątkami, wyświetlając komunikaty o błędach użytkownikowi w formularzu, a najlepiej używając tylko istniejące instalacje Spring Boot i Thymeleaf w wersji barebone.Przesyłanie plików w Spring Boot: przesyłanie, sprawdzanie poprawności i obsługa wyjątków

Używanie przykładowego projektu gs-uploading-files Mogę przesyłać pliki na serwer przy użyciu Spring Boot i Thymeleaf. W application.properties ustawiam spring.http.multipart.max-file-size=1MB i spring.http.multipart.max-request-size=1MB. Podczas przesyłania plików większych niż 1 MB nie rozwiązano kilku problemów związanych z zabezpieczeniami i sprawdzaniem poprawności.

  1. Można przesłać dowolny plik. Na przykład plik html może zostać przesłany, a tym samym hostowany na serwerze. W jaki sposób pliki mogą być ograniczone według typu? Czy można je zweryfikować na stronie przed wysłaniem żądania? Jeśli mam wiele sposobów przesyłania zdjęć, w jaki sposób mogę sprawdzić wszystkie pliki MultipartFiles?

  2. Użytkownicy mogą próbować przenosić duże pliki poza domyślne limity Spring i osadzonego Tomcat. Powoduje to, że org.springframework.web.multipart.MultipartException nie jest obsługiwane przez sprężynę. Jak można sprawdzić rozmiar pliku przed próbą uploadu? W przypadku, gdy jest to pominięte, czy jakikolwiek plik może przesyłać wyjątki wychwycone przez Spring, aby wyświetlał się miły komunikat o błędzie?

  3. Domyślna strona błędu sprężyny nie jest używana jako rezerwowy dla wszystkich wyjątków. Wyjątek MultipartException zwraca stronę wyjątku Tomcat z pełną ścieżką stosu (zobacz Dziennik 1).


Rozglądałem się, aby spróbować znaleźć i wdrożyć szereg rozwiązań.

Krokiem w kierunku ustalenia numeru 1 jest zmiana w rodzaju zawartości, odrzucenie plików, które zawiodły: !file.getContentType().toLowerCase().startsWith("image"). Czy to zawsze będzie ważne? Czy złośliwy użytkownik może ominąć to? I w jaki sposób mogę sprawdzić każdy plik MultipartFile, aby oszczędzić konieczności pamiętania o dodaniu go za każdym razem?

@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:/"; 
} 

Dodawanie @ExceptionHandler nie działa, to po prostu nigdy nie jest wywoływana.

@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; 
} 

Numer 3 może być rozwiązany przez globalną procedurę obsługi wyjątku we wszystkich wyjątkach. (wyjaśniono szczegółowo w this post). Obawiam się jednak, że może to zastąpić kontroler poziomu kontrolera.

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; 
    } 
} 

Mam próbował this answer, który obsługuje wyjątek ale zwraca stronę błędu. Chciałbym powrócić do oryginalnej strony i wyświetlić miły komunikat o błędzie.


Log 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 

Odpowiedz

1

spróbuj dodać następujące w swojej aplikacji.Właściwości, aby ustawić limit rozmiar pliku:

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

Źródło: https://spring.io/guides/gs/uploading-files/

+0

Ustawiłem te (patrz akapit drugi). Chociaż uniemożliwiają przesyłanie dużych plików, nie dają one sensownych komunikatów o błędach ani informacji zwrotnych od użytkowników, i nie ma możliwości sprawdzenia poprawności typu pliku. – aSemy

0

Aby odpowiedzieć na jak sprawdzić typów plików: Stworzyłem własny walidator do tego.

Najpierw utwórz adnotację:

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 {}; 
} 

Następnie należy utworzyć walidatora sam:

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"); 
    } 
} 

Wreszcie zastosowanie adnotacji:

public class CreateUserParameters { 

    @NotNull 
    @ValidImage 
    private MultipartFile image; 
... 
} 

ja testowałem to z wiosny Rozruch 1.5.10 (również z Thymeleaf)

Dla maksymalnego rozmiaru pliku, chciałbym również zobaczyć rozwiązanie, które działa z "standardowym mechanizmem błędu", dzięki czemu można wyświetlić błąd, podobnie jak inne błędy w terenie, a użytkownik może poprawić swój błąd.