mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-13 05:43:29 +00:00
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<>());
|
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
|
* Allows configuring how an anonymous user is represented. This is automatically
|
||||||
* applied when used in conjunction with {@link WebSecurityConfigurerAdapter}. By
|
* 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.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
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.csrf;
|
||||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
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.redirectedUrl;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
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
|
@Test
|
||||||
public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherThenException() {
|
public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherThenException() {
|
||||||
assertThatThrownBy(() -> this.spring.register(NullMatcherConfig.class).autowire())
|
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
|
@Test
|
||||||
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLogoutFilter() {
|
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLogoutFilter() {
|
||||||
this.spring.register(ObjectPostProcessorConfig.class).autowire();
|
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
|
// SEC-3170
|
||||||
@Test
|
@Test
|
||||||
public void configureWhenLogoutHandlerNullThenException() {
|
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
|
// SEC-3170
|
||||||
@Test
|
@Test
|
||||||
public void rememberMeWhenRememberMeServicesNotLogoutHandlerThenRedirectsToLogin() throws Exception {
|
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.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.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.request.MockMvcRequestBuilders.post;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
|
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.redirectedUrl;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests to verify that all the functionality of <logout> attributes is present
|
* 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
|
* 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
|
* 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) {
|
ResultMatcher authenticated(boolean authenticated) {
|
||||||
return result -> assertThat(
|
return result -> assertThat(
|
||||||
Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
|
Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user