2016-11-01 24 views
5

Tôi muốn có thể tải hình ảnh lên máy chủ, xử lý lỗi và ngoại lệ một cách duyên dáng, với thông báo lỗi được hiển thị cho người dùng trong biểu mẫu và lý tưởng chỉ sử dụng một thanh công cụ có sẵn Spring Boot và Thymeleaf đang cài đặt.Tải lên tệp trong Spring Boot: Tải lên, xác thực và xử lý ngoại lệ

Sử dụng dự án mẫu gs-uploading-files Tôi có thể tải tệp lên máy chủ bằng Spring Boot và Thymeleaf. Trong application.properties tôi đặt spring.http.multipart.max-file-size=1MBspring.http.multipart.max-request-size=1MB. Tuy nhiên, một số vấn đề về bảo mật và xác thực không được giải quyết khi tôi tải lên tệp lớn hơn 1MB.

  1. Mọi tệp đều có thể được tải lên. Ví dụ: một tệp html có thể được tải lên và do đó được lưu trữ trên máy chủ. Các tệp có thể bị hạn chế theo loại như thế nào? Họ có thể được xác thực trong trang trước khi yêu cầu được gửi không? Nếu tôi có nhiều cách để tải lên hình ảnh thì tôi có thể xác thực tất cả các MultipartFiles như thế nào?

  2. Người dùng có thể tìm cách tải lên các tệp lớn, vượt quá giới hạn mặc định của Spring và Tomcat được nhúng. Điều này gây ra một org.springframework.web.multipart.MultipartException không được xử lý bởi Spring. Kích thước tệp có thể được xác thực như thế nào trước khi tải lên? Trong trường hợp này là bỏ qua có thể bất kỳ tập tin tải lên ngoại lệ bị bắt bởi mùa xuân, để một thông báo lỗi tốt đẹp được hiển thị?

  3. Trang lỗi mùa xuân mặc định không được sử dụng làm dự phòng cho tất cả các ngoại lệ. MultipartException trả về một trang ngoại lệ Tomcat với một stacktrace đầy đủ (xem Log 1).


Tôi đã tìm kiếm để thử và tìm và thực hiện một loạt các giải pháp.

Bước tiến tới sửa chữa số 1 là sửa đổi thành handleFileUpload kiểm tra loại nội dung, từ chối các tệp không thành công: !file.getContentType().toLowerCase().startsWith("image"). Điều này sẽ luôn luôn hợp lệ? Người dùng độc hại có thể bỏ qua điều này không? Và làm thế nào tôi có thể kiểm tra mọi MultipartFile, để tiết kiệm phải nhớ để thêm này mỗi lần?

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

Thêm @ExceptionHandler không hoạt động, nó không bao giờ được gọi.

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

Số 3 có thể được xử lý bởi một trình xử lý ngoại lệ toàn cục trên tất cả Ngoại lệ. (được giải thích chi tiết trong this post). Tuy nhiên, tôi lo ngại rằng nó có thể overrule một trình điều khiển mức điều khiển.

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

Tôi đã cố gắng this answer, xử lý ngoại lệ nhưng trả về trang lỗi. Tôi muốn quay lại trang gốc và hiển thị thông báo lỗi đẹp.


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 

Trả lời

1

Hãy thử thêm những điều sau đây trong ứng dụng của bạn.tính để thiết lập các giới hạn kích thước tập tin:

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

Nguồn: https://spring.io/guides/gs/uploading-files/

+0

Tôi đã đặt những mục này (xem đoạn hai). Mặc dù chúng không cho phép tải lên các tệp lớn nhưng chúng không cung cấp thông báo lỗi hợp lý hoặc phản hồi của người dùng và không có cách nào để xác thực loại tệp. – aSemy

0

Để trả lời về cách kiểm tra các loại tập tin: Tôi đã tạo ra một validator tùy chỉnh cho việc này.

Đầu tiên, tạo một chú thích:

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

Tiếp theo, tạo validator bản thân:

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

Cuối cùng, áp dụng các chú thích:

public class CreateUserParameters { 

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

Tôi đã thử nghiệm này với mùa xuân Boot 1.5.10 (Cùng với Thymeleaf)

Đối với kích thước tệp tối đa, tôi cũng muốn xem giải pháp hoạt động với "cơ chế lỗi chuẩn" để bạn có thể hiển thị lỗi như các lỗi trường khác và người dùng có thể sửa lỗi của mình.

Các vấn đề liên quan