Skip to content

WebTestClient authentication fails with form-data credentials #10841

Open
@membersound

Description

@membersound

spring-boot-2.6.3

I'm migrating my MockMvc tests to WebTestClient, for having all my tests using the same underlying API.

The following example project shows that authenticating on the /login page works with MockMvc, but does not with WebTestClient.
In real world, I'm testing a ldap security configuration, but the issue is reproducible even with in-memory authentication.

This is a result result of spring-projects/spring-boot#29825
(see the issue also for a full sample project attached)

I assume this is a bug, as authentication with MockMvc works flawless, and WebTestClient does not.

Tests:

@SpringBootTest
@AutoConfigureMockMvc
public class PersonControllerTest {
	@Autowired
	private MockMvc mockMvc;

	@Autowired
	private WebTestClient webTestClient;

	//works
	@Test
	public void testMockMvc() throws Exception {
		SecurityMockMvcRequestBuilders.FormLoginRequestBuilder login = formLogin()
				.user("junituser")
				.password("junitpw");

		 mockMvc.perform(login)
				.andExpect(authenticated().withUsername("junituser"));
	}

	//works
	@Test
	public void testMockMvcUnauthenticated() throws Exception {
		SecurityMockMvcRequestBuilders.FormLoginRequestBuilder login = formLogin()
				.user("junituser")
				.password("invalid");

		mockMvc.perform(login)
				.andExpect(unauthenticated());
	}

        //works
	@Test
	public void testRedirectToLoginPage() {
		webTestClient.get().uri("/").exchange().expectStatus().is3xxRedirection();
	}

        //works
	@Test
	public void testLoginPageAnonymous() {
		webTestClient.get().uri("/login").exchange().expectStatus().isOk();
	}

	//fails with 403 forbidden
	@Test
	public void testWebClient() {
		MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
		formData.add("username", "junituser");
		formData.add("password", "junitpw");

		webTestClient.post()
				.uri("/login") //the test would fail the same executed against '/example' @RestController
				.body(BodyInserters.fromFormData(formData))
				.exchange()
				.expectStatus()
				.isOk();
	}

	//throws NPE
	@Test
	public void testWebClientCsrf() {
		MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
		formData.add("username", "junituser");
		formData.add("password", "junitpw");

		//there is no FormLoginRequestBuilder for WebTestClient?
		webTestClient.mutateWith(csrf())
				.post()
				.uri("/login")
				.body(BodyInserters.fromFormData(formData))
				.exchange()
				.expectStatus()
				.isOk();
	}
}

Source:

@RestController
public class PersonController {
	@GetMapping("/example")
	public String example() {
		return "Authorized user";
	}

	@PostMapping("/example")
	public String examplePost() {
		return "Authorized user";
	}
}

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
				.withUser("junituser")
				.password("{noop}junitpw")
				.roles("USER");
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
				.anyRequest().authenticated()
				.and()
				.formLogin().permitAll();
	}
}

@SpringBootApplication
public class MainApp {
	public static void main(String[] args) {
		SpringApplication.run(MainApp.class, args);
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    for: team-attentionThis ticket should be discussed as a team before proceedingin: testAn issue in spring-security-testtype: bugA general bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions