Allow configuration of session management through nested builder

Issue: gh-5557
This commit is contained in:
Eleftheria Stein 2019-06-27 15:46:52 -04:00
parent 6fd515813c
commit 6fbea88e1e
2 changed files with 128 additions and 0 deletions

View File

@ -530,6 +530,66 @@ public final class HttpSecurity extends
return getOrApply(new SessionManagementConfigurer<>()); 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 * Allows configuring a {@link PortMapper} that is available from
* {@link HttpSecurity#getSharedObject(Class)}. Other provided * {@link HttpSecurity#getSharedObject(Class)}. Other provided

View File

@ -54,6 +54,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.SecurityMockMvcRequestPostProcessors.csrf; 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.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 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 @Test
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSessionManagementFilter() { public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSessionManagementFilter() {
ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class);