MultipartConfig with Servlet 3.0 on Spring MVC

edwardmlyte picture edwardmlyte · Apr 22, 2014 · Viewed 25.2k times · Source

How do I add in multipart configuration to a spring mvc app which uses controllers with methods annotated with RequestMapping?

Background:

I want to enable csrf protection and so have added the security:csrf tag in my spring config. I have a controller class with a method annotated with RequestMapping used for uploading files. I also followed the caveat instructions around multipart whereby I added the multipart filter above the security filter. When I tried to upload a file after adding the csrf tag I got an exception around a missing getParts() method. A quick google highlighted this was due to using a version of jetty which was based on the servlet 2.5 spec. I upgraded jetty-maven-plugin to 8.1.14.v20131031 and tried uploading again. Resulting in:

org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: No multipart config for servlet
        at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:68)
        at org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:58)
        at org.springframework.web.multipart.support.MultipartFilter.doFilterInternal(MultipartFilter.java:110)

Where do I put the multipart configuration for xml setup? All documentation says to add the multipart-config in the servlet tag for the specific servlet in web.xml. There is only a single servlet for my application though. So I added it to that and still I get the same issue.

<servlet>
    <servlet-name>SpringDispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <multipart-config>
        <location>/tmp</location>
        <max-file-size>20848820</max-file-size>
        <max-request-size>418018841</max-request-size>
        <file-size-threshold>1048576</file-size-threshold>
    </multipart-config>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/servlet-context.xml
        </param-value>
    </init-param>
    <load-on-startup>10</load-on-startup>
</servlet>

I also updated the schema location at the top of web.xml to point at version 3.0 of the servlet spec, sourced from http://www.mkyong.com/web-development/the-web-xml-deployment-descriptor-examples/.

Any help?

Edit: added the following riles for Rob:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/spring/webapp.xml</param-value>
    </context-param>

    <context-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>OracleDB,common</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <listener>
        <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>SpringDispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <multipart-config>
            <location>/tmp</location>
            <max-file-size>20848820</max-file-size>
            <max-request-size>418018841</max-request-size>
            <file-size-threshold>1048576</file-size-threshold>
        </multipart-config>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/servlet-context.xml
            </param-value>
        </init-param>
        <load-on-startup>10</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>SpringDispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>MultipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>MultipartFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <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>
    </filter-mapping>

    <filter>
        <filter-name>sitemesh</filter-name>
        <filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>sitemesh</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>XSS</filter-name>
        <filter-class>com.mycompany.CrossScriptingFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>XSS</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <session-config>
        <session-timeout>10</session-timeout>
    </session-config>

    <resource-ref>
        <description>Core Datasource</description>
        <res-ref-name>jdbc/coreDataSource</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>

    <resource-ref>
        <description>Location Datasource</description>
        <res-ref-name>jdbc/locationDataSource</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>

    <error-page>
        <!-- Missing login -->
        <error-code>401</error-code>
        <location>/WEB-INF/views/errorPage.jsp</location>
    </error-page>
    <error-page>
        <!-- Forbidden directory listing -->
        <error-code>403</error-code>
        <location>/WEB-INF/views/errorPage.jsp</location>
    </error-page>
    <error-page>
        <!-- Missing resource -->
        <error-code>404</error-code>
        <location>/WEB-INF/views/errorPageNotFound.jsp</location>
    </error-page>
    <error-page>
        <!-- Uncaught exception -->
        <error-code>500</error-code>
        <location>/WEB-INF/views/errorPage.jsp</location>
    </error-page>
    <error-page>
        <!-- Unsupported servlet method -->
        <error-code>503</error-code>
        <location>/WEB-INF/views/errorPage.jsp</location>
    </error-page>

</web-app>

servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util" xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">

    <mvc:annotation-driven />

    <mvc:resources mapping="/resources/**" location="/resources/" />
    <mvc:resources mapping="/images/**" location="file:${fileSystemStore.fileSystemStorageLocation}"/>

    <context:component-scan base-package="com.mycompany.console.*" />

    <mvc:interceptors>
        <bean class="com.mycompany.security.ChangePasswordInterceptor" />
    </mvc:interceptors>

    <security:global-method-security
        secured-annotations="enabled" jsr250-annotations="enabled"
        pre-post-annotations="enabled" proxy-target-class="true" />

    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
        <property name="contentType" value="text/html;charset=UTF-8" />
    </bean>

    <bean id="filterMultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="100000000"/>
    </bean>

    <bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>classpath:language</value>
                <value>classpath:language_additions</value>
                <value>classpath:formats</value>
            </list>
        </property>
        <property name="defaultEncoding" value="UTF-8" />
    </bean>

    <bean id="localeResolver" class="com.mycompany.locale.SessionLocaleResolver"/>

</beans>

Answer

Rob Winch picture Rob Winch · Apr 22, 2014

I believe you are having issues related to SPR-11373. Specifically, the servlet specification is not clear on what should happen when performing multipart resolution within a Filter.

Have you tried using commons-fileupload instead? This is likely your best option. First add the following dependency:

<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.2.2</version>
</dependency>

Next ensure you have the following bean definition in your root application context.

<bean id="filterMultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="100000000"/>
</bean>

You can find a complete working example with both commons-upload (prefer this solution) and using tomcat using allowCasualMultipartParsing on SEC-2471