diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java index f0924515631..1210fc5eb7b 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; @@ -60,6 +61,7 @@ * No shared objects are used. * * @author Rob Winch + * @author Kazuki Shimizu * @since 3.2 * @see RememberMeConfigurer */ @@ -85,7 +87,7 @@ public LogoutConfigurer() { } /** - * Adds a {@link LogoutHandler}. The {@link SecurityContextLogoutHandler} is added as + * Adds a {@link LogoutHandler}. {@link SecurityContextLogoutHandler} and {@link LogoutSuccessEventPublishingLogoutHandler} are added as * the last {@link LogoutHandler} by default. * * @param logoutHandler the {@link LogoutHandler} to add @@ -329,6 +331,7 @@ List getLogoutHandlers() { */ private LogoutFilter createLogoutFilter(H http) throws Exception { logoutHandlers.add(contextLogoutHandler); + logoutHandlers.add(postProcess(new LogoutSuccessEventPublishingLogoutHandler())); LogoutHandler[] handlers = logoutHandlers .toArray(new LogoutHandler[logoutHandlers.size()]); LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers); diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java index ef0ecd7faee..bc4d09d779e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java @@ -42,6 +42,7 @@ import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy; +import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.NullSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; @@ -54,6 +55,7 @@ import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy; import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * Allows configuring session management. @@ -88,6 +90,7 @@ * * * @author Rob Winch + * @author Kazuki Shimizu * @since 3.2 * @see SessionManagementFilter * @see ConcurrentSessionFilter @@ -471,7 +474,6 @@ public void configure(H http) throws Exception { http.addFilter(sessionManagementFilter); if (isConcurrentSessionControlEnabled()) { ConcurrentSessionFilter concurrentSessionFilter = createConccurencyFilter(http); - concurrentSessionFilter = postProcess(concurrentSessionFilter); http.addFilter(concurrentSessionFilter); } @@ -480,11 +482,20 @@ public void configure(H http) throws Exception { private ConcurrentSessionFilter createConccurencyFilter(H http) { SessionInformationExpiredStrategy expireStrategy = getExpiredSessionStrategy(); SessionRegistry sessionRegistry = getSessionRegistry(http); + ConcurrentSessionFilter concurrentSessionFilter; if(expireStrategy == null) { - return new ConcurrentSessionFilter(sessionRegistry); - } - - return new ConcurrentSessionFilter(sessionRegistry, expireStrategy); + concurrentSessionFilter = new ConcurrentSessionFilter(sessionRegistry); + } else { + concurrentSessionFilter = new ConcurrentSessionFilter(sessionRegistry, expireStrategy); + } + @SuppressWarnings("unchecked") + LogoutConfigurer logoutConf = http.getConfigurer(LogoutConfigurer.class); + List logoutHandlers = logoutConf == null ? null : logoutConf + .getLogoutHandlers(); + if (!CollectionUtils.isEmpty(logoutHandlers)) { + concurrentSessionFilter.setLogoutHandlers(logoutHandlers); + } + return concurrentSessionFilter; } /** diff --git a/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java index 881af31c096..f387f6448f2 100644 --- a/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler; import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.util.StringUtils; import org.w3c.dom.Element; @@ -32,6 +33,7 @@ /** * @author Luke Taylor * @author Ben Alex + * @author Kazuki Shimizu */ class LogoutBeanDefinitionParser implements BeanDefinitionParser { static final String ATT_LOGOUT_SUCCESS_URL = "logout-success-url"; @@ -120,6 +122,8 @@ public BeanDefinition parse(Element element, ParserContext pc) { logoutHandlers.add(cookieDeleter); } + logoutHandlers.add(new RootBeanDefinition(LogoutSuccessEventPublishingLogoutHandler.class)); + builder.addConstructorArgValue(logoutHandlers); return builder.getBeanDefinition(); diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy index 3ca43f1040a..14af6d57d24 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,16 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter import org.springframework.security.web.authentication.RememberMeServices import org.springframework.security.web.authentication.logout.LogoutFilter +import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler import org.springframework.security.web.authentication.logout.LogoutSuccessHandler +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler +import org.springframework.security.web.csrf.CsrfLogoutHandler import org.springframework.security.web.util.matcher.RequestMatcher /** * * @author Rob Winch + * @author Kazuki Shimizu */ class LogoutConfigurerTests extends BaseSpringSpec { @@ -251,4 +255,21 @@ class LogoutConfigurerTests extends BaseSpringSpec { @EnableWebSecurity static class LogoutXMLHttpRequestConfig extends WebSecurityConfigurerAdapter { } + + def "LogoutConfigurer logout handler by default configuration"() { + when: + loadConfig(DefaultWebSecurityConfig) + then: + def logoutFilter = findFilter(LogoutFilter) + logoutFilter.handler.logoutHandlers.size() == 3 + logoutFilter.handler.logoutHandlers[0].class == CsrfLogoutHandler + logoutFilter.handler.logoutHandlers[1].class == SecurityContextLogoutHandler + logoutFilter.handler.logoutHandlers[2].class == LogoutSuccessEventPublishingLogoutHandler + logoutFilter.handler.logoutHandlers[2].applicationEventPublisher != null + + } + @EnableWebSecurity + static class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter { + } + } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy index a904e51c615..706bc06b9a1 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.security.config.annotation.web.configurers import groovy.transform.CompileStatic +import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler import javax.servlet.ServletException import javax.servlet.ServletRequest @@ -43,6 +44,7 @@ import org.springframework.security.web.servletapi.SecurityContextHolderAwareReq /** * * @author Rob Winch + * @author Kazuki Shimizu */ class ServletApiConfigurerTests extends BaseSpringSpec { @@ -71,7 +73,7 @@ class ServletApiConfigurerTests extends BaseSpringSpec { and: "requestFactory != null" filter.requestFactory != null and: "logoutHandlers populated" - filter.logoutHandlers.collect { it.class } == [CsrfLogoutHandler, SecurityContextLogoutHandler] + filter.logoutHandlers.collect { it.class } == [CsrfLogoutHandler, SecurityContextLogoutHandler, LogoutSuccessEventPublishingLogoutHandler] } diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.groovy index 58a91a67019..3ded78c5fea 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,13 @@ */ package org.springframework.security.config.annotation.web.configurers +import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler +import org.springframework.security.web.csrf.CsrfLogoutHandler + import javax.servlet.http.HttpServletResponse import org.springframework.mock.web.MockFilterChain -import org.springframework.mock.web.MockHttpServletRequest -import org.springframework.mock.web.MockHttpServletResponse import org.springframework.security.authentication.AuthenticationTrustResolver import org.springframework.security.config.annotation.AnyObjectPostProcessor import org.springframework.security.config.annotation.BaseSpringSpec @@ -44,6 +46,7 @@ import org.springframework.security.web.session.SessionManagementFilter /** * * @author Rob Winch + * @author Kazuki Shimizu */ class SessionManagementConfigurerTests extends BaseSpringSpec { @@ -250,4 +253,26 @@ class SessionManagementConfigurerTests extends BaseSpringSpec { .setSharedObject(AuthenticationTrustResolver, TR) } } + + def "logout handler by default configuration"() { + when: + loadConfig(ConcurrencyWebSecurityConfig) + then: + def concurrentSessionFilter = findFilter(ConcurrentSessionFilter) + concurrentSessionFilter.handlers.logoutHandlers.size() == 3 + concurrentSessionFilter.handlers.logoutHandlers[0].class == CsrfLogoutHandler + concurrentSessionFilter.handlers.logoutHandlers[1].class == SecurityContextLogoutHandler + concurrentSessionFilter.handlers.logoutHandlers[2].class == LogoutSuccessEventPublishingLogoutHandler + concurrentSessionFilter.handlers.logoutHandlers[2].applicationEventPublisher != null + } + + @EnableWebSecurity + static class ConcurrencyWebSecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + super.configure(http) + http.sessionManagement().maximumSessions(1) + } + } + } diff --git a/config/src/test/groovy/org/springframework/security/config/http/RememberMeConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/RememberMeConfigTests.groovy index 7217cd11662..9ad830c5072 100644 --- a/config/src/test/groovy/org/springframework/security/config/http/RememberMeConfigTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/http/RememberMeConfigTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package org.springframework.security.config.http +import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler + import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML import javax.sql.DataSource @@ -42,6 +44,7 @@ import org.springframework.security.web.authentication.rememberme.TokenBasedReme * @author Luke Taylor * @author Rob Winch * @author Oliver Becker + * @author Kazuki Shimizu */ class RememberMeConfigTests extends AbstractHttpConfigTests { @@ -110,8 +113,11 @@ class RememberMeConfigTests extends AbstractHttpConfigTests { rmp != null 5000 == FieldUtils.getFieldValue(rememberMeServices(), "tokenValiditySeconds") // SEC-909 - logoutHandlers.size() == 2 + logoutHandlers.size() == 3 + logoutHandlers.get(0) instanceof SecurityContextLogoutHandler logoutHandlers.get(1) == rememberMeServices() + logoutHandlers.get(2) instanceof LogoutSuccessEventPublishingLogoutHandler + logoutHandlers.get(2).applicationEventPublisher != null // SEC-1281 rmp.key == "ourkey" } @@ -128,9 +134,11 @@ class RememberMeConfigTests extends AbstractHttpConfigTests { expect: rememberMeServices - logoutHandlers.size() == 2 + logoutHandlers.size() == 3 logoutHandlers.get(0) instanceof SecurityContextLogoutHandler logoutHandlers.get(1) == rememberMeServices + logoutHandlers.get(2) instanceof LogoutSuccessEventPublishingLogoutHandler + logoutHandlers.get(2).applicationEventPublisher != null } def rememberMeTokenValidityIsParsedCorrectly() { diff --git a/config/src/test/groovy/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.groovy index de68710f253..773ce64a46d 100644 --- a/config/src/test/groovy/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,13 @@ */ package org.springframework.security.config.http -import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML +import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler -import java.io.IOException; +import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse import org.springframework.mock.web.MockFilterChain import org.springframework.mock.web.MockHttpServletRequest @@ -43,6 +41,7 @@ import org.springframework.security.web.servletapi.SecurityContextHolderAwareReq /** * * @author Rob Winch + * @author Kazuki Shimizu */ class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTests { @@ -57,8 +56,10 @@ class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTes expect: securityContextAwareFilter.authenticationEntryPoint.loginFormUrl == getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl securityContextAwareFilter.authenticationManager == getFilter(UsernamePasswordAuthenticationFilter).authenticationManager - securityContextAwareFilter.logoutHandlers.size() == 1 + securityContextAwareFilter.logoutHandlers.size() == 2 securityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler + securityContextAwareFilter.logoutHandlers[1].class == LogoutSuccessEventPublishingLogoutHandler + securityContextAwareFilter.logoutHandlers[1].applicationEventPublisher != null } def explicitEntryPoint() { @@ -112,16 +113,20 @@ class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTes securityContextAwareFilter.authenticationEntryPoint.loginFormUrl == '/login' securityContextAwareFilter.authenticationManager == getFilters('/first/filters').find { it instanceof UsernamePasswordAuthenticationFilter}.authenticationManager securityContextAwareFilter.authenticationManager.parent == appContext.getBean('authManager') - securityContextAwareFilter.logoutHandlers.size() == 1 + securityContextAwareFilter.logoutHandlers.size() == 2 securityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler securityContextAwareFilter.logoutHandlers[0].invalidateHttpSession == true + securityContextAwareFilter.logoutHandlers[1].class == LogoutSuccessEventPublishingLogoutHandler + securityContextAwareFilter.logoutHandlers[1].applicationEventPublisher != null secondSecurityContextAwareFilter.authenticationEntryPoint.loginFormUrl == '/login2' secondSecurityContextAwareFilter.authenticationManager == getFilter(UsernamePasswordAuthenticationFilter).authenticationManager secondSecurityContextAwareFilter.authenticationManager.parent == appContext.getBean('authManager2') - securityContextAwareFilter.logoutHandlers.size() == 1 + secondSecurityContextAwareFilter.logoutHandlers.size() == 2 secondSecurityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler secondSecurityContextAwareFilter.logoutHandlers[0].invalidateHttpSession == false + secondSecurityContextAwareFilter.logoutHandlers[1].class == LogoutSuccessEventPublishingLogoutHandler + secondSecurityContextAwareFilter.logoutHandlers[1].applicationEventPublisher != null } def logoutCustom() { @@ -137,11 +142,13 @@ class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTes expect: securityContextAwareFilter.authenticationEntryPoint.loginFormUrl == getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl securityContextAwareFilter.authenticationManager == getFilter(UsernamePasswordAuthenticationFilter).authenticationManager - securityContextAwareFilter.logoutHandlers.size() == 2 + securityContextAwareFilter.logoutHandlers.size() == 3 securityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler securityContextAwareFilter.logoutHandlers[0].invalidateHttpSession == false securityContextAwareFilter.logoutHandlers[1].class == CookieClearingLogoutHandler securityContextAwareFilter.logoutHandlers[1].cookiesToClear == ['JSESSIONID'] + securityContextAwareFilter.logoutHandlers[2].class == LogoutSuccessEventPublishingLogoutHandler + securityContextAwareFilter.logoutHandlers[2].applicationEventPublisher != null } def 'SEC-2926: Role Prefix is set'() { diff --git a/config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy index 56be20884a9..2701faf662d 100644 --- a/config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import org.springframework.security.web.authentication.RememberMeServices import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler import org.springframework.security.web.authentication.logout.LogoutFilter +import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy @@ -54,6 +55,7 @@ import static org.mockito.Mockito.verify * * @author Luke Taylor * @author Rob Winch + * @author Kazuki Shimizu */ class SessionManagementConfigTests extends AbstractHttpConfigTests { @@ -168,10 +170,12 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests { getFilter(SessionManagementFilter.class) != null sessionRegistryIsValid(); - concurrentSessionFilter.handlers.logoutHandlers.size() == 1 + concurrentSessionFilter.handlers.logoutHandlers.size() == 2 def logoutHandler = concurrentSessionFilter.handlers.logoutHandlers[0] logoutHandler instanceof SecurityContextLogoutHandler logoutHandler.invalidateHttpSession + def publishingLogoutHandler = concurrentSessionFilter.handlers.logoutHandlers[1] + publishingLogoutHandler.applicationEventPublisher != null } @@ -192,13 +196,15 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests { def logoutHandlers = concurrentSessionFilter.handlers.logoutHandlers then: 'ConcurrentSessionFilter contains the customized LogoutHandlers' - logoutHandlers.size() == 3 + logoutHandlers.size() == 4 def securityCtxlogoutHandler = logoutHandlers.find { it instanceof SecurityContextLogoutHandler } securityCtxlogoutHandler.invalidateHttpSession == false def cookieClearingLogoutHandler = logoutHandlers.find { it instanceof CookieClearingLogoutHandler } cookieClearingLogoutHandler.cookiesToClear == ['testCookie'] def remembermeLogoutHandler = logoutHandlers.find { it instanceof RememberMeServices } remembermeLogoutHandler == getFilter(RememberMeAuthenticationFilter.class).rememberMeServices + def publishingLogoutHandler = logoutHandlers.find { it instanceof LogoutSuccessEventPublishingLogoutHandler } + publishingLogoutHandler.applicationEventPublisher != null } def 'concurrency-control with remember-me and no LogoutFilter contains SecurityContextLogoutHandler and RememberMeServices as LogoutHandlers'() { diff --git a/core/src/main/java/org/springframework/security/authentication/event/LogoutSuccessEvent.java b/core/src/main/java/org/springframework/security/authentication/event/LogoutSuccessEvent.java new file mode 100644 index 00000000000..bf485e0e2d9 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/event/LogoutSuccessEvent.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.authentication.event; + +import org.springframework.security.core.Authentication; + +/** + * The application event which indicates successful logout. + * + * @author Kazuki Shimizu + * @since 4.2 + */ +public class LogoutSuccessEvent extends AbstractAuthenticationEvent { + + /** + * Constructs a new logout success event. + * + * @param authentication the authentication object + */ + public LogoutSuccessEvent(Authentication authentication) { + super(authentication); + } + +} \ No newline at end of file diff --git a/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.java b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.java new file mode 100644 index 00000000000..98c81cb31cd --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.authentication.logout; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.security.authentication.event.LogoutSuccessEvent; +import org.springframework.security.core.Authentication; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * A logout handler which publish a logout success event. + * + * @author Kazuki Shimizu + * @since 4.2 + */ +public final class LogoutSuccessEventPublishingLogoutHandler + implements LogoutHandler, ApplicationEventPublisherAware { + + private ApplicationEventPublisher applicationEventPublisher; + + /** + * {@inheritDoc} + */ + public void logout(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) { + if (authentication == null) { + return; + } + if (this.applicationEventPublisher == null) { + return; + } + this.applicationEventPublisher.publishEvent(new LogoutSuccessEvent(authentication)); + } + + /** + * {@inheritDoc} + */ + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + +} diff --git a/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java b/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java index 64d6c98e0f2..1a382a24665 100644 --- a/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java +++ b/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.security.web.session; import java.io.IOException; +import java.util.List; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -61,6 +62,7 @@ * @author Ben Alex * @author EddĂș MelĂ©ndez * @author Marten Deinum + * @author Kazuki Shimizu */ public class ConcurrentSessionFilter extends GenericFilterBean { @@ -177,6 +179,15 @@ public void setLogoutHandlers(LogoutHandler[] handlers) { this.handlers = new CompositeLogoutHandler(handlers); } + /** + * Set list of {@link LogoutHandler}. + * @param handlers list of {@link LogoutHandler} + * @since 4.2 + */ + public void setLogoutHandlers(List handlers) { + this.handlers = new CompositeLogoutHandler(handlers); + } + /** * Sets the {@link RedirectStrategy} used with {@link #ConcurrentSessionFilter(SessionRegistry, String)} * @param redirectStrategy the {@link RedirectStrategy} to use diff --git a/web/src/test/java/org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandlerTests.java b/web/src/test/java/org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandlerTests.java new file mode 100644 index 00000000000..f2095822a87 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandlerTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.web.authentication.logout; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.authentication.event.LogoutSuccessEvent; +import org.springframework.security.core.Authentication; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test cases for the {@link LogoutSuccessEventPublishingLogoutHandler}. + * + * @author Kazuki Shimizu + */ +public class LogoutSuccessEventPublishingLogoutHandlerTests { + + private LogoutSuccessEventPublishingLogoutHandler handler; + + private AnnotationConfigApplicationContext context; + private MockHttpServletRequest request; + private MockHttpServletResponse response; + + @Before + public void setUp() { + this.handler = new LogoutSuccessEventPublishingLogoutHandler(); + this.context = new AnnotationConfigApplicationContext(LocalContext.class); + handler.setApplicationEventPublisher(context); + + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + } + + @After + public void tearDown() { + context.close(); + } + + @Test + public void logout() { + Authentication authentication = new TestingAuthenticationToken("test", "password"); + + handler.logout(request, response, authentication); + + EventHandler eventHandler = context.getBean(EventHandler.class); + assertThat(eventHandler.event.getAuthentication()).isSameAs(authentication); + } + + @Test + public void authenticationIsNull() { + Authentication authentication = null; + + handler.logout(request, response, authentication); + + EventHandler eventHandler = context.getBean(EventHandler.class); + assertThat(eventHandler.called).isFalse(); + } + + @Test + public void applicationEventPublisherIsNull() { + Authentication authentication = new TestingAuthenticationToken("test", "password"); + handler.setApplicationEventPublisher(null); + + handler.logout(request, response, authentication); + + EventHandler eventHandler = context.getBean(EventHandler.class); + assertThat(eventHandler.called).isFalse(); + } + + @Configuration + static class LocalContext { + @Bean + EventHandler eventHandler() { + return new EventHandler(); + } + } + + private static class EventHandler { + boolean called; + LogoutSuccessEvent event; + + @EventListener + public void on(LogoutSuccessEvent event) { + called = true; + this.event = event; + } + } + +} diff --git a/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java b/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java index 43789b87131..f421ecd64b3 100644 --- a/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java @@ -316,7 +316,7 @@ public void doFilterWhenCustomLogoutHandlersThenHandlersUsed() throws Exception public void setLogoutHandlersWhenNullThenThrowsException() { ConcurrentSessionFilter filter = new ConcurrentSessionFilter(new SessionRegistryImpl()); - filter.setLogoutHandlers(null); + filter.setLogoutHandlers((LogoutHandler[])null); } @Test(expected = IllegalArgumentException.class)