Ver código fonte

Allow configuration of session management through nested builder

Issue: gh-5557
Eleftheria Stein 6 anos atrás
pai
commit
6fbea88e1e

+ 60 - 0
config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

@@ -530,6 +530,66 @@ public final class HttpSecurity extends
 		return getOrApply(new SessionManagementConfigurer<>());
 	}
 
+	/**
+	 * Allows configuring of Session Management.
+	 *
+	 * <h2>Example Configuration</h2>
+	 *
+	 * The following configuration demonstrates how to enforce that only a single instance
+	 * of a user is authenticated at a time. If a user authenticates with the username
+	 * "user" without logging out and an attempt to authenticate with "user" is made the
+	 * first session will be forcibly terminated and sent to the "/login?expired" URL.
+	 *
+	 * <pre>
+	 * &#064;Configuration
+	 * &#064;EnableWebSecurity
+	 * public class SessionManagementSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	&#064;Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.authorizeRequests()
+	 * 				.anyRequest().hasRole(&quot;USER&quot;)
+	 * 				.and()
+	 * 			.formLogin(formLogin ->
+	 * 				formLogin
+	 * 					.permitAll()
+	 * 			)
+	 * 			.sessionManagement(sessionManagement ->
+	 * 				sessionManagement
+	 * 					.maximumSessions(1)
+	 * 					.expiredUrl(&quot;/login?expired&quot;)
+	 * 			);
+	 * 	}
+	 * }
+	 * </pre>
+	 *
+	 * When using {@link SessionManagementConfigurer#maximumSessions(int)}, do not forget
+	 * to configure {@link HttpSessionEventPublisher} for the application to ensure that
+	 * expired sessions are cleaned up.
+	 *
+	 * In a web.xml this can be configured using the following:
+	 *
+	 * <pre>
+	 * &lt;listener&gt;
+	 *      &lt;listener-class&gt;org.springframework.security.web.session.HttpSessionEventPublisher&lt;/listener-class&gt;
+	 * &lt;/listener&gt;
+	 * </pre>
+	 *
+	 * Alternatively,
+	 * {@link AbstractSecurityWebApplicationInitializer#enableHttpSessionEventPublisher()}
+	 * could return true.
+	 *
+	 * @param sessionManagementCustomizer the {@link Customizer} to provide more options for
+	 * the {@link SessionManagementConfigurer}
+	 * @return the {@link HttpSecurity} for further customizations
+	 * @throws Exception
+	 */
+	public HttpSecurity sessionManagement(Customizer<SessionManagementConfigurer<HttpSecurity>> sessionManagementCustomizer) throws Exception {
+		sessionManagementCustomizer.customize(getOrApply(new SessionManagementConfigurer<>()));
+		return HttpSecurity.this;
+	}
+
 	/**
 	 * Allows configuring a {@link PortMapper} that is available from
 	 * {@link HttpSecurity#getSharedObject(Class)}. Other provided

+ 68 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java

@@ -54,6 +54,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.SecurityMockMvcRequestPostProcessors.csrf;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -262,6 +263,73 @@ public class SessionManagementConfigurerTests {
 		}
 	}
 
+	@Test
+	public void loginWhenUserLoggedInAndMaxSessionsOneInLambdaThenLoginPrevented() throws Exception {
+		this.spring.register(ConcurrencyControlInLambdaConfig.class).autowire();
+
+		this.mvc.perform(post("/login")
+				.with(csrf())
+				.param("username", "user")
+				.param("password", "password"));
+
+		this.mvc.perform(post("/login")
+				.with(csrf())
+				.param("username", "user")
+				.param("password", "password"))
+				.andExpect(status().isFound())
+				.andExpect(redirectedUrl("/login?error"));
+	}
+
+	@EnableWebSecurity
+	static class ConcurrencyControlInLambdaConfig extends WebSecurityConfigurerAdapter {
+		@Override
+		public void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.formLogin(withDefaults())
+				.sessionManagement(sessionManagement ->
+					sessionManagement
+						.maximumSessions(1)
+						.maxSessionsPreventsLogin(true)
+				);
+			// @formatter:on
+		}
+
+		@Override
+		protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+			// @formatter:off
+			auth
+				.inMemoryAuthentication()
+					.withUser(PasswordEncodedUser.user());
+			// @formatter:on
+		}
+	}
+
+	@Test
+	public void requestWhenSessionCreationPolicyStateLessInLambdaThenNoSessionCreated() throws Exception {
+		this.spring.register(SessionCreationPolicyStateLessInLambdaConfig.class).autowire();
+
+		MvcResult mvcResult = this.mvc.perform(get("/"))
+				.andReturn();
+		HttpSession session = mvcResult.getRequest().getSession(false);
+
+		assertThat(session).isNull();
+	}
+
+	@EnableWebSecurity
+	static class SessionCreationPolicyStateLessInLambdaConfig extends WebSecurityConfigurerAdapter {
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.sessionManagement(sessionManagement ->
+					sessionManagement
+						.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+				);
+			// @formatter:on
+		}
+	}
+
 	@Test
 	public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSessionManagementFilter() {
 		ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class);