在servlet过滤器中处理MaxUploadSizeExceededException,如何在重定向中设置flash消息?

时间:2016-04-19 18:09:43

标签: spring spring-security multipartform-data

在实施Spring Security和CSRF保护时,出现了一些新问题,尤其是多部分文件上传问题。阅读有关此问题的几个主题,我遇到了以下问题的以下解决方案:

首先,问题的原因是在启用CSRF后上传文件不再有效(实际上在Spring SEcurity中默认启用)。 因为在使用多部分请求时不发送CSRF令牌,所以必须在Spring Security过滤器之前指定MultipartFilter。 根据Spring Security(http://docs.spring.io/spring-security/site/docs/4.0.4.RELEASE/reference/htmlsingle/#csrf-multipartfilter)的文档,这可以通过在SecurityWebApplicationInitializer类中实现 beforeSpringSecurityFilterChain 方法来完成:

public class SecurityWebApplicationInitializer extends
    AbstractSecurityWebApplicationInitializer {

  @Override
    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
        // TODO Auto-generated method stub
      insertFilters(servletContext, new MultipartFilter());
    }

}

这不会立即起作用,而是抛出异常,经过一些研究后,似乎已配置的 MultipartFilter 是一个ServletFilter,通过在根应用程序上下文中查找MultipartResolver来解析多部分请求。默认名称“filterMultipartResolver”。

因此,必须修改以下声明并将其从MvcConfig移至AppConfig类:

// was moved from MvcConfig in to fix multipart exception 
  //THe bean MUST be has an id of filterMultipartResolver to be picked up by the
  //MultipartFilter configured in the beforeSpringSecurityFilterChain 
  //(see SecurityWebApplicationInitializer)
  @Bean(name="filterMultipartResolver")
    public CommonsMultipartResolver multipartResolver() {
      CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
      multipartResolver.setMaxUploadSize(2000000);
      multipartResolver.setMaxInMemorySize(500000);
      return multipartResolver;
  }

现在文件上传按预期再次运行但稍后我发现当文件太大时抛出的MaxUploadSizeExceededException不再由我的ExceptionHandlingControllerAdvice类中的@ExceptionHandler方法处理,而是以丑陋的HTTP返回Tomcat的500状态页面。

原因是,由于MultipartFilter,在请求到达DispatcherServlet之前抛出异常,因此永远不会调用ControllerAdvice。

要继续这一系列问题和解决方法,在阅读有关此问题的各种帖子后,我实现了一个专用的Filter来捕获并处理异常并将其放入过滤器链中:

public class MultipartExceptionHandler extends OncePerRequestFilter {


private static Logger logger = LoggerFactory.getLogger(MultipartExceptionHandler.class);


  @Override
  protected void doFilterInternal(HttpServletRequest req,
        HttpServletResponse res, FilterChain filterChain)
        throws ServletException, IOException {

    try {
        filterChain.doFilter(req, res);
    } catch(MaxUploadSizeExceededException me) {
        handle(req, res, me);
    } catch(ServletException se) {
        if (se.getRootCause() instanceof MaxUploadSizeExceededException) {
            handle(req, res, (MaxUploadSizeExceededException) se.getRootCause());
        } else {
            throw se;
        }
    }

  }

  private void handle(HttpServletRequest req, HttpServletResponse res,
        MaxUploadSizeExceededException me) throws ServletException, IOException {
    // TODO Auto-generated method stub
    logger.info("MaxUploadSizeExceededException is handled in custom filter");      
    String redirect = UrlUtils.buildFullRequestUrl(req);
    req.getSession().setAttribute(KeyConstants.FLASH_ERROR_KEY, "File is too big!");
    res.sendRedirect(redirect);
  }
}

将MultipartExceptionHandler过滤器添加到过滤器链中,方法是将其添加到insertFilters方法中:

@Override
  protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
    // TODO Auto-generated method stub
    insertFilters(servletContext, new MultipartExceptionHandler(), new MultipartFilter());
  }

现在,MaxUploadSizeExceededException确实被过滤器捕获,并显示重定向的页面。 但是,问题是response.sendRedirect方法没有设置错误flash消息的功能。 在最初的@ExceptionHandler方法中,这是以下列方式进行的:

    RedirectView rv = new RedirectView(redirectUrl);
    FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(req);
    if (outputFlashMap != null) {
        outputFlashMap.put(KeyConstants.FLASH_ERROR_KEY, "File is too large");
    }
    return rv;

但是在新的异常处理程序过滤器中,这不起作用。对RequestContextUtils.getOutputFlashMap(req)的调用返回null。

因此,我在执行重定向之前在会话中手动设置了Flash消息,现在它已显示,但它仍保留在会话中,因此必须在完成重定向后在某处明确删除。这对我来说似乎是不好的做法,但我还没有找到更好的解决方案。 在开始使用带有CSRF保护的SpringSecurity之后,经历了相当长时间的一系列问题,但我希望有更好的解决方案来设置flash消息。

1 个答案:

答案 0 :(得分:0)

不显示Flash消息的问题可能是由于使用:

FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(req);

根据文档,RequestContextUtilsUtility是一个类"用于轻松访问由DispatcherServlet设置的特定于请求的状态。" 因为我们在到达DispatcherServlet之前处理异常,所以getOutputFlashMap方法可能返回null。

使用以下代码时,会生成flash消息!

FlashMapManager flashMapManager = new SessionFlashMapManager();
FlashMap fm = new FlashMap();
fm.put(KeyConstants.FLASH_ERROR_KEY, "File is too large");
flashMapManager.saveOutputFlashMap(fm, req, res);