Browse Source

Allow configuration of form login through nested builder

Issue: gh-5557
Eleftheria Stein 6 years ago
parent
commit
a9a1f8ee53

+ 66 - 2
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.
+	 *
+	 * <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.
 	 * <br>

+ 2 - 2
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
 		}

+ 147 - 0
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);