Allow configuration of logout through nested builder

Issue: gh-5557
This commit is contained in:
Eleftheria Stein 2019-06-19 12:55:26 -04:00
parent 43737a56bd
commit 92314b0956
3 changed files with 209 additions and 2 deletions

View File

@ -773,6 +773,53 @@ public final class HttpSecurity extends
return getOrApply(new LogoutConfigurer<>());
}
/**
* Provides logout support. This is automatically applied when using
* {@link WebSecurityConfigurerAdapter}. The default is that accessing the URL
* "/logout" will log the user out by invalidating the HTTP Session, cleaning up any
* {@link #rememberMe()} authentication that was configured, clearing the
* {@link SecurityContextHolder}, and then redirect to "/login?success".
*
* <h2>Example Custom Configuration</h2>
*
* The following customization to log out when the URL "/custom-logout" is invoked.
* Log out will remove the cookie named "remove", not invalidate the HttpSession,
* clear the SecurityContextHolder, and upon completion redirect to "/logout-success".
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class LogoutSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .authorizeRequests()
* .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
* .and()
* .formLogin()
* .and()
* // sample logout customization
* .logout(logout ->
* logout.deleteCookies(&quot;remove&quot;)
* .invalidateHttpSession(false)
* .logoutUrl(&quot;/custom-logout&quot;)
* .logoutSuccessUrl(&quot;/logout-success&quot;)
* );
* }
* }
* </pre>
*
* @param logoutCustomizer the {@link Customizer} to provide more options for
* the {@link LogoutConfigurer}
* @return the {@link HttpSecurity} for further customizations
* @throws Exception
*/
public HttpSecurity logout(Customizer<LogoutConfigurer<HttpSecurity>> logoutCustomizer) throws Exception {
logoutCustomizer.customize(getOrApply(new LogoutConfigurer<>()));
return HttpSecurity.this;
}
/**
* Allows configuring how an anonymous user is represented. This is automatically
* applied when used in conjunction with {@link WebSecurityConfigurerAdapter}. By

View File

@ -37,10 +37,15 @@ import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
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.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -77,6 +82,26 @@ public class LogoutConfigurerTests {
}
}
@Test
public void configureWhenDefaultLogoutSuccessHandlerForHasNullLogoutHandlerInLambdaThenException() {
assertThatThrownBy(() -> this.spring.register(NullLogoutSuccessHandlerInLambdaConfig.class).autowire())
.isInstanceOf(BeanCreationException.class)
.hasRootCauseInstanceOf(IllegalArgumentException.class);
}
@EnableWebSecurity
static class NullLogoutSuccessHandlerInLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.logout(logout ->
logout.defaultLogoutSuccessHandlerFor(null, mock(RequestMatcher.class))
);
// @formatter:on
}
}
@Test
public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherThenException() {
assertThatThrownBy(() -> this.spring.register(NullMatcherConfig.class).autowire())
@ -96,6 +121,26 @@ public class LogoutConfigurerTests {
}
}
@Test
public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherInLambdaThenException() {
assertThatThrownBy(() -> this.spring.register(NullMatcherInLambdaConfig.class).autowire())
.isInstanceOf(BeanCreationException.class)
.hasRootCauseInstanceOf(IllegalArgumentException.class);
}
@EnableWebSecurity
static class NullMatcherInLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.logout(logout ->
logout.defaultLogoutSuccessHandlerFor(mock(LogoutSuccessHandler.class), null)
);
// @formatter:on
}
}
@Test
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLogoutFilter() {
this.spring.register(ObjectPostProcessorConfig.class).autowire();
@ -263,6 +308,29 @@ public class LogoutConfigurerTests {
}
}
@Test
public void logoutWhenCustomLogoutUrlInLambdaThenRedirectsToLogin() throws Exception {
this.spring.register(CsrfDisabledAndCustomLogoutInLambdaConfig.class).autowire();
this.mvc.perform(get("/custom/logout"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/login?logout"));
}
@EnableWebSecurity
static class CsrfDisabledAndCustomLogoutInLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.csrf()
.disable()
.logout(logout -> logout.logoutUrl("/custom/logout"));
// @formatter:on
}
}
// SEC-3170
@Test
public void configureWhenLogoutHandlerNullThenException() {
@ -283,6 +351,24 @@ public class LogoutConfigurerTests {
}
}
@Test
public void configureWhenLogoutHandlerNullInLambdaThenException() {
assertThatThrownBy(() -> this.spring.register(NullLogoutHandlerInLambdaConfig.class).autowire())
.isInstanceOf(BeanCreationException.class)
.hasRootCauseInstanceOf(IllegalArgumentException.class);
}
@EnableWebSecurity
static class NullLogoutHandlerInLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.logout(logout -> logout.addLogoutHandler(null));
// @formatter:on
}
}
// SEC-3170
@Test
public void rememberMeWhenRememberMeServicesNotLogoutHandlerThenRedirectsToLogin() throws Exception {

View File

@ -41,9 +41,11 @@ import org.springframework.test.web.servlet.ResultMatcher;
import static org.assertj.core.api.Assertions.assertThat;
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.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests to verify that all the functionality of <logout> attributes is present
@ -83,6 +85,23 @@ public class NamespaceHttpLogoutTests {
}
}
@Test
@WithMockUser
public void logoutWhenDisabledInLambdaThenRespondsWithNotFound() throws Exception {
this.spring.register(HttpLogoutDisabledInLambdaConfig.class).autowire();
this.mvc.perform(post("/logout").with(csrf()).with(user("user")))
.andExpect(status().isNotFound());
}
@EnableWebSecurity
static class HttpLogoutDisabledInLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout(AbstractHttpConfigurer::disable);
}
}
/**
* http/logout custom
*/
@ -112,6 +131,35 @@ public class NamespaceHttpLogoutTests {
}
}
@Test
@WithMockUser
public void logoutWhenUsingVariousCustomizationsInLambdaThenMatchesNamespace() throws Exception {
this.spring.register(CustomHttpLogoutInLambdaConfig.class).autowire();
this.mvc.perform(post("/custom-logout").with(csrf()))
.andExpect(authenticated(false))
.andExpect(redirectedUrl("/logout-success"))
.andExpect(result -> assertThat(result.getResponse().getCookies()).hasSize(1))
.andExpect(cookie().maxAge("remove", 0))
.andExpect(session(Objects::nonNull));
}
@EnableWebSecurity
static class CustomHttpLogoutInLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.logout(logout ->
logout.deleteCookies("remove")
.invalidateHttpSession(false)
.logoutUrl("/custom-logout")
.logoutSuccessUrl("/logout-success")
);
// @formatter:on
}
}
/**
* http/logout@success-handler-ref
*/
@ -141,6 +189,32 @@ public class NamespaceHttpLogoutTests {
}
}
@Test
@WithMockUser
public void logoutWhenUsingSuccessHandlerRefInLambdaThenMatchesNamespace() throws Exception {
this.spring.register(SuccessHandlerRefHttpLogoutInLambdaConfig.class).autowire();
this.mvc.perform(post("/logout").with(csrf()))
.andExpect(authenticated(false))
.andExpect(redirectedUrl("/SuccessHandlerRefHttpLogoutConfig"))
.andExpect(noCookies())
.andExpect(session(Objects::isNull));
}
@EnableWebSecurity
static class SuccessHandlerRefHttpLogoutInLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
logoutSuccessHandler.setDefaultTargetUrl("/SuccessHandlerRefHttpLogoutConfig");
// @formatter:off
http
.logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler));
// @formatter:on
}
}
ResultMatcher authenticated(boolean authenticated) {
return result -> assertThat(
Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())