Allow configuration of form login through nested builder

Issue: gh-5557
This commit is contained in:
Eleftheria Stein 2019-06-27 14:29:11 -04:00
parent 758397f102
commit a9a1f8ee53
3 changed files with 215 additions and 4 deletions

View File

@ -962,8 +962,7 @@ public final class HttpSecurity extends
* .authorizeRequests() * .authorizeRequests()
* .antMatchers("/**").hasRole("USER") * .antMatchers("/**").hasRole("USER")
* .and() * .and()
* .formLogin() * .formLogin(withDefaults())
* .and()
* // sample logout customization * // sample logout customization
* .logout(logout -> * .logout(logout ->
* logout.deleteCookies("remove") * logout.deleteCookies("remove")
@ -1112,6 +1111,71 @@ public final class HttpSecurity extends
return getOrApply(new FormLoginConfigurer<>()); return getOrApply(new FormLoginConfigurer<>());
} }
/**
* Specifies to support form based authentication. If
* {@link FormLoginConfigurer#loginPage(String)} is not specified a default login page
* will be generated.
*
* <h2>Example Configurations</h2>
*
* The most basic configuration defaults to automatically generating a login page at
* the URL "/login", redirecting to "/login?error" for authentication failure. The
* details of the login page can be found on
* {@link FormLoginConfigurer#loginPage(String)}
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .authorizeRequests()
* .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
* .and()
* .formLogin(withDefaults());
* }
* }
* </pre>
*
* The configuration below demonstrates customizing the defaults.
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .authorizeRequests()
* .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
* .and()
* .formLogin(formLogin ->
* formLogin
* .usernameParameter(&quot;username&quot;)
* .passwordParameter(&quot;password&quot;)
* .loginPage(&quot;/authentication/login&quot;)
* .failureUrl(&quot;/authentication/login?failed&quot;)
* .loginProcessingUrl(&quot;/authentication/login/process&quot;)
* );
* }
* }
* </pre>
*
* @see FormLoginConfigurer#loginPage(String)
*
* @param formLoginCustomizer the {@link Customizer} to provide more options for
* the {@link FormLoginConfigurer}
* @return the {@link HttpSecurity} for further customizations
* @throws Exception
*/
public HttpSecurity formLogin(Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer) throws Exception {
formLoginCustomizer.customize(getOrApply(new FormLoginConfigurer<>()));
return HttpSecurity.this;
}
/** /**
* Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider. * Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider.
* <br> * <br>

View File

@ -55,6 +55,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
@ -528,8 +529,7 @@ public class CsrfConfigurerTests {
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
// @formatter:off // @formatter:off
http http
.formLogin() .formLogin(withDefaults())
.and()
.csrf(csrf -> csrf.csrfTokenRepository(REPO)); .csrf(csrf -> csrf.csrfTokenRepository(REPO));
// @formatter:on // @formatter:on
} }

View File

@ -42,6 +42,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
@ -195,6 +196,81 @@ public class FormLoginConfigurerTests {
} }
} }
@Test
public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultUsernameAndPasswordParameterNames() throws Exception {
this.spring.register(FormLoginInLambdaConfig.class).autowire();
this.mockMvc.perform(formLogin().user("username", "user").password("password", "password"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/"));
}
@Test
public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultFailureUrl() throws Exception {
this.spring.register(FormLoginInLambdaConfig.class).autowire();
this.mockMvc.perform(formLogin().user("invalid"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/login?error"));
}
@Test
public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultSuccessUrl() throws Exception {
this.spring.register(FormLoginInLambdaConfig.class).autowire();
this.mockMvc.perform(formLogin())
.andExpect(status().isFound())
.andExpect(redirectedUrl("/"));
}
@Test
public void getLoginPageWhenFormLoginDefaultsInLambdaThenNotSecured() throws Exception {
this.spring.register(FormLoginInLambdaConfig.class).autowire();
this.mockMvc.perform(get("/login"))
.andExpect(status().isOk());
}
@Test
public void loginWhenFormLoginDefaultsInLambdaThenSecured() throws Exception {
this.spring.register(FormLoginInLambdaConfig.class).autowire();
this.mockMvc.perform(post("/login"))
.andExpect(status().isForbidden());
}
@Test
public void requestProtectedWhenFormLoginDefaultsInLambdaThenRedirectsToLogin() throws Exception {
this.spring.register(FormLoginInLambdaConfig.class).autowire();
this.mockMvc.perform(get("/private"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
}
@EnableWebSecurity
static class FormLoginInLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.formLogin(withDefaults());
// @formatter:on
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.inMemoryAuthentication()
.withUser(PasswordEncodedUser.user());
// @formatter:on
}
}
@Test @Test
public void getLoginPageWhenFormLoginPermitAllThenPermittedAndNoRedirect() throws Exception { public void getLoginPageWhenFormLoginPermitAllThenPermittedAndNoRedirect() throws Exception {
this.spring.register(FormLoginConfigPermitAll.class).autowire(); this.spring.register(FormLoginConfigPermitAll.class).autowire();
@ -297,6 +373,33 @@ public class FormLoginConfigurerTests {
} }
} }
@Test
public void getLoginPageWhenCustomLoginPageInLambdaThenPermittedAndNoRedirect() throws Exception {
this.spring.register(FormLoginDefaultsInLambdaConfig.class).autowire();
this.mockMvc.perform(get("/authenticate"))
.andExpect(redirectedUrl(null));
}
@EnableWebSecurity
static class FormLoginDefaultsInLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.formLogin(formLogin ->
formLogin
.loginPage("/authenticate")
.permitAll()
)
.logout(LogoutConfigurer::permitAll);
// @formatter:on
}
}
@Test @Test
public void loginWhenCustomLoginProcessingUrlThenRedirectsToHome() throws Exception { public void loginWhenCustomLoginProcessingUrlThenRedirectsToHome() throws Exception {
this.spring.register(FormLoginLoginProcessingUrlConfig.class).autowire(); this.spring.register(FormLoginLoginProcessingUrlConfig.class).autowire();
@ -340,6 +443,50 @@ public class FormLoginConfigurerTests {
} }
} }
@Test
public void loginWhenCustomLoginProcessingUrlInLambdaThenRedirectsToHome() throws Exception {
this.spring.register(FormLoginLoginProcessingUrlInLambdaConfig.class).autowire();
this.mockMvc.perform(formLogin("/loginCheck"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/"));
}
@EnableWebSecurity
static class FormLoginLoginProcessingUrlInLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin(formLogin ->
formLogin
.loginProcessingUrl("/loginCheck")
.loginPage("/login")
.defaultSuccessUrl("/", true)
.permitAll()
)
.logout(logout ->
logout
.logoutSuccessUrl("/login")
.logoutUrl("/logout")
.deleteCookies("JSESSIONID")
);
// @formatter:on
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.inMemoryAuthentication()
.withUser(PasswordEncodedUser.user());
// @formatter:on
}
}
@Test @Test
public void requestWhenCustomPortMapperThenPortMapperUsed() throws Exception { public void requestWhenCustomPortMapperThenPortMapperUsed() throws Exception {
FormLoginUsesPortMapperConfig.PORT_MAPPER = mock(PortMapper.class); FormLoginUsesPortMapperConfig.PORT_MAPPER = mock(PortMapper.class);