Spring Security 3.2 CSRF support for multipart requests

Find most answer is answered server years ago.

If you need

Passing CSRF tokens with RestTemplate

This blog is quite enlightening https://cloudnative.tips/passing-csrf-tokens-with-resttemplate-736b336a6cf6

In Spring Security 5.0.7.RELEASE

https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-multipart

There are two options to using CSRF protection with multipart/form-data. Each option has its tradeoffs.

-Placing MultipartFilter before Spring Security
-Include CSRF token in action

For short, the first option is safer, the latter is easier.

Specifying the MultipartFilter before the Spring Security filter means that there is no authorization for invoking the MultipartFilter which means anyone can place temporary files on your server. However, only authorized users will be able to submit a File that is processed by your application. In general, this is the recommended approach because the temporary file upload should have a negligible impact on most servers.

To ensure MultipartFilter is specified before the Spring Security filter with java configuration, users can override beforeSpringSecurityFilterChain as shown below:

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

  @Override
  protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
      insertFilters(servletContext, new MultipartFilter());
  }
}

To ensure MultipartFilter is specified before the Spring Security filter with XML configuration, users can ensure the element of the MultipartFilter is placed before the springSecurityFilterChain within the web.xml as shown below:

<filter>
  <filter-name>MultipartFilter</filter-name>
  <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>MultipartFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

Another option

If allowing unauthorized users to upload temporary files is not acceptable, an alternative is to place the MultipartFilter after the Spring Security filter and include the CSRF as a query parameter in the action attribute of the form. An example with a jsp is shown below

<form action="./upload?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">

The disadvantage to this approach is that query parameters can be leaked. More genearlly, it is considered best practice to place sensitive data within the body or headers to ensure it is not leaked.


I was able to resolve this with help from the Spring Security team. I have updated the Gist to reflect a working configuration. I had to follow the steps given below in order to get everything to work as expected.


1. Common Step

Add a MultipartFilter to web.xml as described in the answer by @holmis83, ensuring that it is added before the Spring Security configuration:

<filter>
    <display-name>springMultipartFilter</display-name>
    <filter-name>springMultipartFilter</filter-name>
    <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>springMultipartFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
    <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>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

2.1. Using Apache Commons Multipart Resolver

Ensure that there is an Apache Commons Multipart Resolver bean named filterMultipartResolver in the root Spring application context. I will stress this again, make sure that the Multipart Resolver is declared in the root Spring Context (usually called applicationContext.xml). For example,

web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath*:springWebMultipartContext.xml
    </param-value>
</context-param>

springWebMultipartContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="filterMultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="100000000" />
    </bean>
</beans>

Make sure that the bean is called filterMultipartResolver as any other bean name is not picked up by MultipartFilter configured in web.xml. My initial configuration was not working because this bean was named multipartResolver. I even tried passing the bean name to MultipartFilter using web.xml init-param but that did not work either.

2.2. Using Tomcat Multipart support

Tomcat 7.0+ has in-built multipart support, but it has to be explicitly enabled. Either change the global Tomcat context.xml file as follows or include a local context.xml file in your WAR file for this support to work without making any other changes to your application.

<Context allowCasualMultipartParsing="true">
    ...
</Context>

After these changes using Apache Commons Multipart Resolver our application is working so far on Tomcat, Jetty and Weblogic.


This part:

<filter-mapping>
    <filter-name>multipartFilter</filter-name>
    <servlet-name>/*</servlet-name>
</filter-mapping>

Should be:

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

It is an error in Spring Security 3.2.0 documentation. The bug has been reported and will be fixed in upcoming version.


After struggling with this issue a bit, I found a much easier solution by just using the Request Header defined in Spring Security instead of trying to get the CSRF token embedded as a part of the multipart content.

Here is a simple way I setup the header using an AJAX library for file upload in my jsp:

var uploader = new AjaxUpload({
        url: '/file/upload',
        name: 'uploadfile',
        multipart: true,
        customHeaders: { '${_csrf.headerName}': '${_csrf.token}' },
        ...
        onComplete: function(filename, response) {
            ...
        },
        onError: function( filename, type, status, response ) {
            ...
        }
});

Which in turn sent the multipart request with header:

X-CSRF-TOKEN: abcdef01-2345-6789-abcd-ef0123456789

Their recommendations for embedding into <meta /> tags in the header would also work just fine by halting the request on submit, adding the header via javascript, and then finish submitting:

<html>
<head>
    <meta name="_csrf" content="${_csrf.token}"/>
    <!-- default header name is X-CSRF-TOKEN -->
    <meta name="_csrf_header" content="${_csrf.headerName}"/>
    <!-- ... -->
</head>
<body>
    <!-- ... -->
    <script>
        var token = $("meta[name='_csrf']").attr("content");
        var header = $("meta[name='_csrf_header']").attr("content");
        // Do whatever with values
    </script>
</body>
</html>

More info: Spring Security - CSRF for AJAX and JSON Requests