Spring Security + Mock MVC Testing - Assertion error, redirect URL "http://localhost/login" instead of "/login"

93 views Asked by At

I have such a test

@Test
@WithAnonymousUser
void givenNoAuthentication_whenEnteringMainPage_thenRedirectToLoginPage() throws Exception {
    mockMvc.perform(get("/")).andExpect(redirectedUrl("/login"));
}

And I got assertion error like this:

java.lang.AssertionError: Redirected URL expected:</login> but was:<http://localhost/login>
Expected :/login
Actual   :http://localhost/login

What is more, I have another test similar to this one which tests if logged in user gets redirected to main page ("/") after accessing /login and this test is working fine

@Test
@WithMockUser
void givenAuthentication_whenEnteringLoginPage_thenRedirectToMainPage() throws Exception {
    mockMvc.perform(get("/login")).andExpect(redirectedUrl("/"));
}

Here is how my spring security config looks like

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
            .authorizeHttpRequests(authorize - > authorize
                .requestMatchers("/css/**").permitAll()
                .requestMatchers("/js/**").permitAll()
                .requestMatchers("/login").permitAll()
                .requestMatchers("/register").permitAll()
                .requestMatchers("/user/register").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(form - > form
                .loginPage("/login")
                .defaultSuccessUrl("/", true)

            )
            .logout(logout - > logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login")
            )
            .build();
    }

    @Bean
    public static PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

How can I fix my first test, and why it doesnt't work when second test which is almost the same is working fine?

EDIT: I managed to fix this by using .endsWith("/login") like so

MvcResult result = mockMvc.perform(get("/")).andReturn();
assertThat(result.getResponse().getRedirectedUrl()).endsWith("/login");

But I still don't understand why in this test redirect url contains hostname, and it other it doesn't

1

There are 1 answers

4
Pistolnikus On BEST ANSWER

While not a definitive answer, maybe my research will help someone as this behavior is a bit annoying when writing tests for /login, because that endpoint behaves differently.

There are some related answers:

I believe the behavior is intentional for two reasons

  1. The behavior only occurs on /login and no other endpoints. org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint.buildRedirectUrlToLoginPage is responsible for this behavior, there is explicit logic that makes the URL absolute (spring.security.web:6.2.1):
if (UrlUtils.isAbsoluteUrl(loginForm)) {
    return loginForm;
}
// ...
// otherwise construct absolute URL
  1. FormLoginConfigurerTests has asserts for redirectedUrl for login set explicitly to test absolute values everywhere. On all other places the tests uses relative paths.

Speculations only:

Maybe it's a bug or was written in that way due to historical reasons - e.g. older RFC 2616 stated to use absolute uris:

Location = "Location" ":" absoluteURI

but the newer RFC 7231 allows relative paths (details in this answer).

Or maybe there is some security concern in mind - forcing to use absolute URI on login so that app have "more" under control whether http or https is used.

Either way, I haven't found a way to configure it to behave differently. So right now I'm forced to test /login endpoints differently than other endpoints.