In my Spring MVC project I am trying to upload a file via a simple form.
HTML Form:
This is covered in the CSRF - Multipart (File Upload) section of the Spring Security reference. You have two options:
Placing MultipartFilter before Spring Security
The first option is to ensure that the MultipartFilter is specified before the Spring Security filter. 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 negligble 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>
Include CSRF token in action
If allowing unauthorized users to upload temporariy 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. Additional information can be found in RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI’s.
The solution work for me is to disable csrf()
in my WebSecurityConfig
like :
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
}
}
And now i can simply upload multipart file :)
For me even with the csrf().disable() its didn't work. Once i disable it i got 200 but the file wasn't uploaded and i didn't see any error. Once i set the debug flag logging.level.org.springframework.web: DEBUG i saw the root cause:
[org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/target/tomcat/work/Tomcat/localhost/ROOT] is not valid]","exception":""}
I tried setting the location on application.yaml: spring.servlet.http.multipart.location: "/tmp" but it didn't change the location so what i did is adding the below code and it did the trick:
@Bean
MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
String location = "/tmp";
LOGGER.debug("Multipart location file:" + location);
File tmpFile = new File(location);
if (!tmpFile.exists()) {
tmpFile.mkdirs();
}
factory.setLocation(location);
return factory.createMultipartConfig();
}
The fast solution for me was the following
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Upload File Request Page</title>
</head>
<body>
<form method="POST" action="file/uploadFile?${_csrf.parameterName}=${_csrf.token}" enctype="multipart/form-data">
File to upload: <input type="file" name="file"><br />
Name: <input type="text" name="name"><br /> <br />
<input type="submit" value="Upload"> Press here to upload the file!
</form>
</body>
</html>
The controller code is the following:
package com.student.controller;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
@Controller
@RequestMapping("/file")
public class FileUploadController {
@RequestMapping(value = "", method = RequestMethod.GET)
public String index(ModelMap modelMap,Principal principal,HttpServletRequest request) {
return "uploadfile";
}
@RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
public @ResponseBody String uploadFileHandler(@RequestParam("name") String name, @RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
try {
byte[] bytes = file.getBytes();
// Creating the directory to store file
String rootPath = System.getProperty("catalina.home");
File dir = new File(rootPath + File.separator + "tmpFiles");
if (!dir.exists())
dir.mkdirs();
// Create the file on server
File serverFile = new File(dir.getAbsolutePath()
+ File.separator + name);
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream(serverFile));
stream.write(bytes);
stream.close();
return "You successfully uploaded file=" + rootPath+name;
} catch (Exception e) {
return "You failed to upload " + name + " => " + e.getMessage();
}
} else {
return "You failed to upload " + name
+ " because the file was empty.";
}
}
}
I added the following code in spring dispatcher file
<!-- upload files -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- setting maximum upload size -->
<property name="maxUploadSize" value="100000" />
</bean>