I created a simple app with Spring Boot and Spring security which contains :
- A login form
- An "upload" form (with an associated Controller on backend's side)
Problem : Spring security has built-in default CSRF protection. It works well with common REST calls but it prevents me from uploading a file : I get this error message :
Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'.
If I deactivate CSRF protection, I can successfully upload the file.
I created a SSCCE to illustrate the problem. The steps to reproduce are :
- Launch the application (Main class is
com.denodev.Application
) - Connect to
localhost:8080
- Authenticate with those credentials :
- Login :
user
- Password :
password
- Login :
- When redirected to the "upload" form, try to upload any file.
- In class
Application
, feel free to activate/deactivate CSRF protection, restart the app and retry.
The relevant part of the code is :
@RestController
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@RequestMapping(value = "/upload-file", method = RequestMethod.POST)
@ResponseBody
public String uploadFile(@RequestParam("file") MultipartFile file) {
return "Successfully received file "+file.getOriginalFilename();
}
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/**/*.html", "login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(successHandler())
.failureHandler(failureHandler())
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.authenticationEntryPoint(authenticationEntryPoint())
.and()
//1 : Uncomment to activate csrf protection
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
//2 : Uncomment to disable csrf protection
//.csrf().disable()
;
}
/**
* Return HTTP 200 on authentication success instead of redirecting to a page.
*/
private AuthenticationSuccessHandler successHandler() {
return new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
}
};
}
/**
* Return HTTP 401 on authentication failure instead of redirecting to a page.
*/
private AuthenticationFailureHandler failureHandler() {
return new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpServletResponse.getWriter().write(e.getMessage());
}
};
}
/**
* Return HTTP 403 on "access denied" instead of redirecting to a page.
*/
private AccessDeniedHandler accessDeniedHandler() {
return new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.getWriter().write(e.getMessage());
}
};
}
private AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpServletResponse.getWriter().write(e.getMessage());
}
};
}
What I tried :
The Spring security's documentation about Multipart advices to place MultipartFilter
before Spring security. It explains well how to do it with a plain old webapp by editing the web.xml
file. This is not applicable to Spring Boot and I cannot figure what is the equivalent syntax.
I tried to expose the MultipartFilter
with annotations @Bean
and Order
with several options but I still struggle with it.
Any ideas?