Describe the bug In project with spring security and thymeleaf on servlet stack there is different behaviour in case of missing CSRF token when attempting login.
To Reproduce Run provided sample and send request from post-login.http file without csrf token, when csrf filter is enabled:
# @no-redirect
POST http://localhost:8080/login
Content-Type: application/x-www-form-urlencoded
username=admin&password=hardPassword
Then change the version of Spring Boot to 3.0.2 run again, and send request again.
Response for Spring Boot 2.7.5 is 403 (in the browser it is error page) Response for Spring Boot 3.0.2 is 302 Found /login (in the browser it is login form)
For some reason MockMvc gets 403 response in both cases as you can see in provided test cases.
Expected behavior Same response in both cases.
Sample https://github.com/slawekludwiczak/spring-security-csrf You can check behaviour on youtube https://youtu.be/K4mYYQUWE9s
Comment From: marcusdacoregio
Hi @slawekludwiczak, this is an expected scenario.
In Spring Security 6.0, there was a change to perform authorization for every dispatcher type, Spring Boot also changed its defaults.
In summary, Spring Boot redirects to /error after the CSRF 403 and:
- In Spring Security 5, the ERROR dispatch is not protected and returns the 403 response.
- In Spring Security 6, the /error is protected and you did not permit the /error endpoint or the DispatcherType.ERROR, therefore the application will return 401 for the /error and Spring Security understands a 401 as an unauthenticated login and redirects to the /login page.
You have a few workarounds for this:
- Add
.requestMatchers("/error").permitAll()to your authorization rules - Add
.dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()to your authorization rules - Remove
errorfromspring.security.filter.dispatcher-typesalthough not recommended
Comment From: slawekludwiczak
Hi @marcusdacoregio can you please verify that mockMvc works as expected in this situation? I would expect that for missing csrf() I will get 302 response, but i get 403 both for spring boot 2.7.x and 3.0.x The test case is provided in sample and simply looks like this
mockMvc.perform(post("/login")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("username", "admin")
.param("password", "hardPassword"))
// .with(csrf()))
.andExpect(status().isFound());
Comment From: marcusdacoregio
Hi @slawekludwiczak,
I would expect that for missing csrf()
I don't think I follow because your test has with(csrf()). Also, I don't understand why you expect a 302, if you do not provide the CSRF token you will have your access denied, therefore a 403 should be expected.
It is also important to mention that MockMvc is different than end-to-end tests:
Things that may catch you by surprise are that there is no context path by default; no jsessionid cookie; no forwarding, error, or async dispatches;
If you want another behavior from MockMvc I recommend you to read the link above and maybe try another approach.
Comment From: slawekludwiczak
with(csrf()) is commented. Thanks for the link, I will check that out
Comment From: marcusdacoregio
I'm sorry, I didn't notice the // 😄