Allow configuration of logout through nested builder
Issue: gh-5557
This commit is contained in:
parent
43737a56bd
commit
92314b0956
|
@ -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>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class LogoutSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
*
|
||||
* @Override
|
||||
* protected void configure(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .authorizeRequests()
|
||||
* .antMatchers("/**").hasRole("USER")
|
||||
* .and()
|
||||
* .formLogin()
|
||||
* .and()
|
||||
* // sample logout customization
|
||||
* .logout(logout ->
|
||||
* logout.deleteCookies("remove")
|
||||
* .invalidateHttpSession(false)
|
||||
* .logoutUrl("/custom-logout")
|
||||
* .logoutSuccessUrl("/logout-success")
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
* </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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Reference in New Issue