spring-projects / spring-projects/spring-security

WebTestClient authentication fails with form-data credentials

Open
#10,841 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

for: team-attention in: test type: bug
Dominant language
Java
Stars
9.6k
Forks
6.3k
Avg merge
2d 11h
Merged PRs (30d)
52

Description

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 https://github.com/spring-projects/spring-boot/issues/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);
	}
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the provided WebTestClient tests and compare their form-data login and CSRF behavior with the working MockMvc tests. Investigate the WebTestClient authentication path described in the issue; done means valid credentials authenticate successfully without 403, while invalid credentials remain unauthenticated and the CSRF test no longer throws an NPE.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring, spring-boot
Domain
backend, security, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.