如何使用 Spring Security 很好地处理文件上传 MaxUploadSizeExceededException
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23856254/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to nicely handle file upload MaxUploadSizeExceededException with Spring Security
提问by xtian
I'm using Spring Web 4.0.5, Spring Security 3.2.4, Commons FileUpload 1.3.1, Tomcat 7 and I'm getting an ugly MaxUploadSizeExceededException
when my upload size limit is exceeded, which results in a "500 Internal Server Error". I handle it with a nice generic popup, but I'd rather have my Controller take care of it by going back to the originating form with the proper explanation message.
我正在使用 Spring Web 4.0.5、Spring Security 3.2.4、Commons FileUpload 1.3.1、Tomcat 7,MaxUploadSizeExceededException
当超过上传大小限制时,我会变得丑陋,这会导致“500 内部服务器错误”。我用一个很好的通用弹出窗口来处理它,但我宁愿让我的控制器通过使用正确的解释消息返回到原始表单来处理它。
I've seen a similar question asked many times, with a few solutions that might work when not using Spring Security; none of the ones I tried worked for me.
我已经多次看到一个类似的问题,其中有一些解决方案在不使用 Spring Security 时可能会起作用;我试过的没有一个对我有用。
The problem might be that when using Spring Security, the CommonsMultipartResolver
is not added as a "multipartResolver" bean but as a "filterMultipartResolver":
问题可能是在使用 Spring Security 时,CommonsMultipartResolver
它不是作为“multipartResolver”bean 添加的,而是作为“filterMultipartResolver”添加的:
@Bean(name="filterMultipartResolver")
CommonsMultipartResolver filterMultipartResolver() {
CommonsMultipartResolver filterMultipartResolver = new CommonsMultipartResolver();
filterMultipartResolver.setMaxUploadSize(MAXSIZE);
return filterMultipartResolver;
}
If I set filterMultipartResolver.setResolveLazily(true);
it makes no difference.
如果我设置filterMultipartResolver.setResolveLazily(true);
它没有区别。
If I subclass the CommonsMultipartResolver
with my own and override the parseRequest()
method with something that traps the MaxUploadSizeExceededException
and returns an empty MultipartParsingResult
, I get a "403 Forbidden" error:
如果我CommonsMultipartResolver
用我自己的子类化 the并parseRequest()
使用捕获 theMaxUploadSizeExceededException
并返回一个空的东西覆盖该方法MultipartParsingResult
,我会收到“403 Forbidden”错误:
public class ExtendedCommonsMultipartResolver extends CommonsMultipartResolver {
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
String encoding = determineEncoding(request);
try {
return super.parseRequest(request);
} catch (MaxUploadSizeExceededException e) {
return parseFileItems(Collections.<FileItem> emptyList(), encoding);
}
}
}
Finally, there's no point in implementing some kind of local or global ExceptionHandler
because it is never called.
最后,实现某种本地或全局ExceptionHandler
是没有意义的,因为它从未被调用过。
If I don't find a better solution, I'll just remove the upload size limit and handle it myself in the controller, with the drawback of having the user wait until the upload is finished before seeing the error message about file size. Of I might even ignore all of this because, being it an image in this case, I could just resize it down to proper values.
如果我找不到更好的解决方案,我将删除上传大小限制并在控制器中自己处理它,缺点是让用户在看到有关文件大小的错误消息之前等待上传完成。我什至可能会忽略所有这些,因为在这种情况下它是一个图像,我可以将其调整为适当的值。
Still, I'd like to see a solution to this problem.
不过,我想看到这个问题的解决方案。
Thank you
谢谢
EDIT:
编辑:
I add the stack trace as requested. This is the case where a 500 is generated.
我按要求添加了堆栈跟踪。这是生成 500 的情况。
May 30, 2014 12:47:17 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/site] threw exception
org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size of 1000000 bytes exceeded; nested exception is org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (3403852) exceeds the configured maximum (1000000)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:162)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.resolveMultipart(CommonsMultipartResolver.java:142)
at org.springframework.web.multipart.support.MultipartFilter.doFilterInternal(MultipartFilter.java:110)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:409)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1044)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:315)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
Caused by: org.apache.commons.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (3403852) exceeds the configured maximum (1000000)
at org.apache.commons.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:965)
at org.apache.commons.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:310)
at org.apache.commons.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:334)
at org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest(ServletFileUpload.java:115)
at org.springframework.web.multipart.commons.CommonsMultipartResolver.parseRequest(CommonsMultipartResolver.java:158)
... 19 more
采纳答案by Rob Winch
You can handle the MaxUploadSizeExceededException by adding an additional Filter to catch the exception and the redirect to an error page. For example, you could create a MultipartExceptionHandler Filter like the following:
您可以通过添加额外的过滤器来处理 MaxUploadSizeExceededException 以捕获异常并重定向到错误页面。例如,您可以创建一个 MultipartExceptionHandler 过滤器,如下所示:
public class MultipartExceptionHandler extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (MaxUploadSizeExceededException e) {
handle(request, response, e);
} catch (ServletException e) {
if(e.getRootCause() instanceof MaxUploadSizeExceededException) {
handle(request, response, (MaxUploadSizeExceededException) e.getRootCause());
} else {
throw e;
}
}
}
private void handle(HttpServletRequest request,
HttpServletResponse response, MaxUploadSizeExceededException e) throws ServletException, IOException {
String redirect = UrlUtils.buildFullRequestUrl(request) + "?error";
response.sendRedirect(redirect);
}
}
NOTE: This redirect makes an assumption about your form and upload. You may need to modify where to redirect to. Specifically if you follow the pattern of your form being at GET and it is processed at POST this will work.
注意:此重定向假设您的表单和上传。您可能需要修改重定向到的位置。具体来说,如果您遵循 GET 表单的模式并在 POST 处理它,这将起作用。
You can then ensure to add this Filter before MultipartFilter. For example, if you are using web.xml you would see something like this:
然后,您可以确保在 MultipartFilter 之前添加此过滤器。例如,如果您使用 web.xml,您将看到如下内容:
<filter>
<filter-name>meh</filter-name>
<filter-class>org.example.web.MultipartExceptionHandler</filter-class>
</filter>
<filter>
<description>
Allows the application to accept multipart file data.
</description>
<display-name>springMultipartFilter</display-name>
<filter-name>springMultipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
<!--init-param>
<param-name>multipartResolverBeanName</param-name>
<param-value>multipartResolver</param-value>
</init-param-->
</filter>
<filter>
<description>
Secures access to web resources using the Spring Security framework.
</description>
<display-name>springSecurityFilterChain</display-name>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>meh</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springMultipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>ERROR</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
In your form you can then detect if the error occurred by inspecting if the HTTP parameter error is present. For example, in a JSP you might do the following:
在您的表单中,您可以通过检查是否存在 HTTP 参数错误来检测是否发生错误。例如,在 JSP 中,您可以执行以下操作:
<c:if test="${param.error != null}">
<p>Failed to upload...too big</p>
</c:if>
PS: I created SEC-2614to update the documentation to discuss error handling
PS:我创建了SEC-2614来更新文档以讨论错误处理
回答by Taugenichts
I know I'm late to the party, but I found a much more elegant solution imho.
我知道我参加聚会迟到了,但恕我直言,我找到了一个更优雅的解决方案。
Instead of adding a filter for the multipart resolver, simply add throws MaxUploadSizeExceededException
on your controller method and add the filter for the DelegatingFilterProxy
in your web.xml
and you can add an exception handler right in your controller without having to redirect the request.
而不是增加一个过滤器为多解析器,只需添加throws MaxUploadSizeExceededException
您的控制器方法,并添加过滤器为DelegatingFilterProxy
您web.xml
和您可以添加在你的控制器中的异常处理程序的权利,而无需将请求重定向。
e.g.:
例如:
Method (in Controller):
方法(在控制器中):
@RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
public ResponseEntity<String> uploadFile(MultipartHttpServletRequest request) throws MaxUploadSizeExceededException {
//code
}
Exception Handler (in same controller):
异常处理程序(在同一个控制器中):
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity handleSizeExceededException(HttpServletRequest request, Exception ex) {
//code
}
Web.xml (thanks to Rob Winch):
Web.xml(感谢 Rob Winch):
<filter>
<description>
Secures access to web resources using the Spring Security framework.
</description>
<display-name>springSecurityFilterChain</display-name>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>ERROR</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
And that is all you need.
这就是你所需要的。
回答by px5x2
The thing is springSecurityFilterChain
must be added aftermultipart filter. That's why you are getting 403 status. Here:
东西是springSecurityFilterChain
必须在多部分过滤器之后添加的。这就是您获得 403 状态的原因。这里:
I think after you do so, you will be able to catch FileUploadBase.SizeLimitExceededException
in a @ControllerAdvice annotated class containing @ExceptionHandler
annotated methods.
我认为这样做之后,您将能够捕获FileUploadBase.SizeLimitExceededException
包含带@ExceptionHandler
注释方法的 @ControllerAdvice 带注释类。
回答by xtian
The solution I came up with while experimenting is the following:
我在试验时提出的解决方案如下:
Extend CommonsMultipartResolver in order to swallow the exception. I add the exception to the Request just in case you want to use it in the Controller, but I don't think it's needed
package org.springframework.web.multipart.commons; import java.util.Collections; import javax.servlet.http.HttpServletRequest; import org.apache.commons.fileupload.FileItem; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; public class ExtendedCommonsMultipartResolver extends CommonsMultipartResolver { @Override protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { try { return super.parseRequest(request); } catch (MaxUploadSizeExceededException e) { request.setAttribute("MaxUploadSizeExceededException", e); return parseFileItems(Collections.<FileItem> emptyList(), null); } } }
Declare your resolver in the WebSecurityConfigurerAdapter, in place of CommonsMultipartResolver (you should declare a filterMultipartResolver in any case so nothing new here)
@Bean(name="filterMultipartResolver") CommonsMultipartResolver filterMultipartResolver() { CommonsMultipartResolver filterMultipartResolver = new ExtendedCommonsMultipartResolver(); filterMultipartResolver.setMaxUploadSize(MAXBYTES); return filterMultipartResolver; }
Remember to define the correct filter precedence in the AbstractSecurityWebApplicationInitializer as stated in the docs (you'd do this in any case)
@Order(1) public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { @Override protected void beforeSpringSecurityFilterChain(ServletContext servletContext) { insertFilters(servletContext, new MultipartFilter()); } }
Add the _csrf token to the form action URL (I'm using thymeleaf here)
<form th:action="@{|/submitImage?${_csrf.parameterName}=${_csrf.token}|}"
In the Controller, just check for null on the MultipartFile, something like (snippet not checked for errors):
@RequestMapping(value = "/submitImage", method = RequestMethod.POST) public String submitImage(MyFormBean myFormBean, BindingResult bindingResult, HttpServletRequest request, Model model) { MultipartFile multipartFile = myFormBean.getImage(); if (multipartFile==null) { bindingResult.rejectValue("image", "validation.image.filesize"); } else if (multipartFile.isEmpty()) { bindingResult.rejectValue("image", "validation.image.missing");
扩展 CommonsMultipartResolver 以吞下异常。我将例外添加到请求中,以防万一您想在控制器中使用它,但我认为不需要
package org.springframework.web.multipart.commons; import java.util.Collections; import javax.servlet.http.HttpServletRequest; import org.apache.commons.fileupload.FileItem; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; public class ExtendedCommonsMultipartResolver extends CommonsMultipartResolver { @Override protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { try { return super.parseRequest(request); } catch (MaxUploadSizeExceededException e) { request.setAttribute("MaxUploadSizeExceededException", e); return parseFileItems(Collections.<FileItem> emptyList(), null); } } }
在 WebSecurityConfigurerAdapter 中声明你的解析器,代替 CommonsMultipartResolver(你应该在任何情况下声明一个 filterMultipartResolver 所以这里没有什么新东西)
@Bean(name="filterMultipartResolver") CommonsMultipartResolver filterMultipartResolver() { CommonsMultipartResolver filterMultipartResolver = new ExtendedCommonsMultipartResolver(); filterMultipartResolver.setMaxUploadSize(MAXBYTES); return filterMultipartResolver; }
请记住按照文档中的说明在 AbstractSecurityWebApplicationInitializer 中定义正确的过滤器优先级(在任何情况下都可以这样做)
@Order(1) public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { @Override protected void beforeSpringSecurityFilterChain(ServletContext servletContext) { insertFilters(servletContext, new MultipartFilter()); } }
将 _csrf 令牌添加到表单操作 URL(我在这里使用 thymeleaf)
<form th:action="@{|/submitImage?${_csrf.parameterName}=${_csrf.token}|}"
在控制器中,只需检查 MultipartFile 上的 null,例如(未检查代码段是否有错误):
@RequestMapping(value = "/submitImage", method = RequestMethod.POST) public String submitImage(MyFormBean myFormBean, BindingResult bindingResult, HttpServletRequest request, Model model) { MultipartFile multipartFile = myFormBean.getImage(); if (multipartFile==null) { bindingResult.rejectValue("image", "validation.image.filesize"); } else if (multipartFile.isEmpty()) { bindingResult.rejectValue("image", "validation.image.missing");
This way you can use the usual Controller method for handling the form submission even in case of size exceeded.
这样,即使在超出大小的情况下,您也可以使用通常的 Controller 方法来处理表单提交。
What I don't like of this approach is that you have to mess with an external library package (MultipartParsingResult is protected) and that you have to remember setting the token on the form url (which is also less secure btw).
我不喜欢这种方法的一点是,您必须弄乱外部库包(MultipartParsingResult 受保护),并且您必须记住在表单 url 上设置令牌(顺便说一句,这也不太安全)。
What I do like is that you handle the form submission in just one place in the controller.
我喜欢的是您只在控制器中的一个地方处理表单提交。
The problem of a big file being fully downloaded before returning to the user also persists, but I guess it is addressed somewhere else already.
在返回给用户之前完全下载大文件的问题仍然存在,但我想它已经在其他地方解决了。