diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 75e818caf9..4c0fae24e0 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -962,8 +962,7 @@ public final class HttpSecurity extends * .authorizeRequests() * .antMatchers("/**").hasRole("USER") * .and() - * .formLogin() - * .and() + * .formLogin(withDefaults()) * // sample logout customization * .logout(logout -> * logout.deleteCookies("remove") @@ -1112,6 +1111,71 @@ public final class HttpSecurity extends 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. + * + *

Example Configurations

+ * + * 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)} + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests()
+	 * 				.antMatchers("/**").hasRole("USER")
+	 * 				.and()
+	 * 			.formLogin(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * The configuration below demonstrates customizing the defaults. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests()
+	 * 				.antMatchers("/**").hasRole("USER")
+	 * 				.and()
+	 * 			.formLogin(formLogin ->
+	 * 				formLogin
+	 * 					.usernameParameter("username")
+	 * 					.passwordParameter("password")
+	 * 					.loginPage("/authentication/login")
+	 * 					.failureUrl("/authentication/login?failed")
+	 * 					.loginProcessingUrl("/authentication/login/process")
+	 * 			);
+	 * 	}
+	 * }
+	 * 
+ * + * @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> 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. *
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java index be2aad0815..22e88597ce 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java @@ -55,6 +55,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; 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.user; 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 { // @formatter:off http - .formLogin() - .and() + .formLogin(withDefaults()) .csrf(csrf -> csrf.csrfTokenRepository(REPO)); // @formatter:on } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java index 4211806094..ae5a2379e4 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java @@ -42,6 +42,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; 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.logout; 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 public void getLoginPageWhenFormLoginPermitAllThenPermittedAndNoRedirect() throws Exception { 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 public void loginWhenCustomLoginProcessingUrlThenRedirectsToHome() throws Exception { 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 public void requestWhenCustomPortMapperThenPortMapperUsed() throws Exception { FormLoginUsesPortMapperConfig.PORT_MAPPER = mock(PortMapper.class);