With csrf disabled I can upload file however I need it enabled. The problem only occurs when the form enctype is multipart/form-data, namely 'Invalid CSRF Token' with 403.
Generally when I set the enctype as multipart/form-data even for a form without file upload, I get the same error.
Using this dependency:
<dependency>
<groupId>org.synchronoss.cloud</groupId>
<artifactId>nio-multipart-parser</artifactId>
<version>...</version>
</dependency>
Tried including hidden csrf input in the form and also tried appending it to the url but same error
<form method="post" th:action="${'/add/' + id + '/documents?' + _csrf.headerName + '=' + _csrf.token}" enctype="multipart/form-data">
<input type="file" name="documents" multiple="multiple">
<input type="hidden"
th:name="${_csrf.headerName}"
th:value="${_csrf.token}" />
<input type="hidden" name="_csrf" th:value="${_csrf.token}">
<button class="btn btn-success btn-l">Upload</button>
</form>
Have a controller advice like this for csrf injection
@ControllerAdvice
public class SecurityAdvice {@ModelAttribute("_csrf")Mono<CsrfToken> csrfToken(final ServerWebExchange exchange) {
final Mono<CsrfToken> csrfToken = exchange.getAttributeOrDefault(org.springframework.security.web.server.csrf.CsrfToken.class.getName(), Mono.empty());
return csrfToken;
}
In security I have the following bean:
@Bean
public ServerCsrfTokenRepository csrfTokenRepository() {
WebSessionServerCsrfTokenRepository repository =
new WebSessionServerCsrfTokenRepository();
repository.setHeaderName("X-CSRF-TK");
return repository;
}
and using it like this in my SecurityWebFilterChain:
.and().csrf().csrfTokenRepository(csrfTokenRepository())
UPDATE:
Disabling csrf for a few urls would be enough too. Found a few examples but all of them are for Servlet based version. https://sdqali.in/blog/2016/07/20/csrf-protection-with-spring-security-and-angular-js/
Take a look at Spring Security's official recommendation: https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-multipart
There are basically two ways of doing it: (1) placing MultipartFilter before Spring Security filter and (2) include the CSRF token in the form action, as you are doing. The first option is the recommended one:
To ensure MultipartFilter is specified before the Spring Security filter with java configuration, users can override beforeSpringSecurityFilterChain as shown below:
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:
Note that, if you still want to use form action, query parameters can be leaked. Try to change your "headerName" to "parameterName":
EDIT: In case you can't switch to a servlet-based container (such as Jetty or Tomcat) and the form action recommendation doesn't work, there's a recent Stack Overflow thread discussing this issue.
One of the developers reported to workaround the issue using AJAX:
The same developer reported this issue to Spring, but didn't get any attention yet.