Skip to content

Commit a1594e6

Browse files
committed
Support new event for indicates logout success
Fixes gh-3307
1 parent 0b1e3b4 commit a1594e6

File tree

14 files changed

+337
-31
lines changed

14 files changed

+337
-31
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurer.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
import org.springframework.security.web.authentication.logout.LogoutFilter;
3131
import org.springframework.security.web.authentication.logout.LogoutHandler;
3232
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
33+
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler;
3334
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
3435
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
3536
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
@@ -60,6 +61,7 @@
6061
* No shared objects are used.
6162
*
6263
* @author Rob Winch
64+
* @author Kazuki Shimizu
6365
* @since 3.2
6466
* @see RememberMeConfigurer
6567
*/
@@ -85,7 +87,7 @@ public LogoutConfigurer() {
8587
}
8688

8789
/**
88-
* Adds a {@link LogoutHandler}. The {@link SecurityContextLogoutHandler} is added as
90+
* Adds a {@link LogoutHandler}. {@link SecurityContextLogoutHandler} and {@link LogoutSuccessEventPublishingLogoutHandler} are added as
8991
* the last {@link LogoutHandler} by default.
9092
*
9193
* @param logoutHandler the {@link LogoutHandler} to add
@@ -329,6 +331,7 @@ List<LogoutHandler> getLogoutHandlers() {
329331
*/
330332
private LogoutFilter createLogoutFilter(H http) throws Exception {
331333
logoutHandlers.add(contextLogoutHandler);
334+
logoutHandlers.add(postProcess(new LogoutSuccessEventPublishingLogoutHandler()));
332335
LogoutHandler[] handlers = logoutHandlers
333336
.toArray(new LogoutHandler[logoutHandlers.size()]);
334337
LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);

config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
4343
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
4444
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
45+
import org.springframework.security.web.authentication.logout.LogoutHandler;
4546
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
4647
import org.springframework.security.web.context.NullSecurityContextRepository;
4748
import org.springframework.security.web.context.SecurityContextRepository;
@@ -54,6 +55,7 @@
5455
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
5556
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;
5657
import org.springframework.util.Assert;
58+
import org.springframework.util.CollectionUtils;
5759

5860
/**
5961
* Allows configuring session management.
@@ -88,6 +90,7 @@
8890
* </ul>
8991
*
9092
* @author Rob Winch
93+
* @author Kazuki Shimizu
9194
* @since 3.2
9295
* @see SessionManagementFilter
9396
* @see ConcurrentSessionFilter
@@ -471,7 +474,6 @@ public void configure(H http) throws Exception {
471474
http.addFilter(sessionManagementFilter);
472475
if (isConcurrentSessionControlEnabled()) {
473476
ConcurrentSessionFilter concurrentSessionFilter = createConccurencyFilter(http);
474-
475477
concurrentSessionFilter = postProcess(concurrentSessionFilter);
476478
http.addFilter(concurrentSessionFilter);
477479
}
@@ -480,11 +482,20 @@ public void configure(H http) throws Exception {
480482
private ConcurrentSessionFilter createConccurencyFilter(H http) {
481483
SessionInformationExpiredStrategy expireStrategy = getExpiredSessionStrategy();
482484
SessionRegistry sessionRegistry = getSessionRegistry(http);
485+
ConcurrentSessionFilter concurrentSessionFilter;
483486
if(expireStrategy == null) {
484-
return new ConcurrentSessionFilter(sessionRegistry);
485-
}
486-
487-
return new ConcurrentSessionFilter(sessionRegistry, expireStrategy);
487+
concurrentSessionFilter = new ConcurrentSessionFilter(sessionRegistry);
488+
} else {
489+
concurrentSessionFilter = new ConcurrentSessionFilter(sessionRegistry, expireStrategy);
490+
}
491+
@SuppressWarnings("unchecked")
492+
LogoutConfigurer<H> logoutConf = http.getConfigurer(LogoutConfigurer.class);
493+
List<LogoutHandler> logoutHandlers = logoutConf == null ? null : logoutConf
494+
.getLogoutHandlers();
495+
if (!CollectionUtils.isEmpty(logoutHandlers)) {
496+
concurrentSessionFilter.setLogoutHandlers(logoutHandlers);
497+
}
498+
return concurrentSessionFilter;
488499
}
489500

490501
/**

config/src/main/java/org/springframework/security/config/http/LogoutBeanDefinitionParser.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,13 +25,15 @@
2525
import org.springframework.beans.factory.xml.ParserContext;
2626
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
2727
import org.springframework.security.web.authentication.logout.LogoutFilter;
28+
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler;
2829
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
2930
import org.springframework.util.StringUtils;
3031
import org.w3c.dom.Element;
3132

3233
/**
3334
* @author Luke Taylor
3435
* @author Ben Alex
36+
* @author Kazuki Shimizu
3537
*/
3638
class LogoutBeanDefinitionParser implements BeanDefinitionParser {
3739
static final String ATT_LOGOUT_SUCCESS_URL = "logout-success-url";
@@ -120,6 +122,8 @@ public BeanDefinition parse(Element element, ParserContext pc) {
120122
logoutHandlers.add(cookieDeleter);
121123
}
122124

125+
logoutHandlers.add(new RootBeanDefinition(LogoutSuccessEventPublishingLogoutHandler.class));
126+
123127
builder.addConstructorArgValue(logoutHandlers);
124128

125129
return builder.getBeanDefinition();

config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* 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
2323
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
2424
import org.springframework.security.web.authentication.RememberMeServices
2525
import org.springframework.security.web.authentication.logout.LogoutFilter
26+
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler
2627
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler
28+
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler
29+
import org.springframework.security.web.csrf.CsrfLogoutHandler
2730
import org.springframework.security.web.util.matcher.RequestMatcher
2831

2932
/**
3033
*
3134
* @author Rob Winch
35+
* @author Kazuki Shimizu
3236
*/
3337
class LogoutConfigurerTests extends BaseSpringSpec {
3438

@@ -251,4 +255,21 @@ class LogoutConfigurerTests extends BaseSpringSpec {
251255
@EnableWebSecurity
252256
static class LogoutXMLHttpRequestConfig extends WebSecurityConfigurerAdapter {
253257
}
258+
259+
def "LogoutConfigurer logout handler by default configuration"() {
260+
when:
261+
loadConfig(DefaultWebSecurityConfig)
262+
then:
263+
def logoutFilter = findFilter(LogoutFilter)
264+
logoutFilter.handler.logoutHandlers.size() == 3
265+
logoutFilter.handler.logoutHandlers[0].class == CsrfLogoutHandler
266+
logoutFilter.handler.logoutHandlers[1].class == SecurityContextLogoutHandler
267+
logoutFilter.handler.logoutHandlers[2].class == LogoutSuccessEventPublishingLogoutHandler
268+
logoutFilter.handler.logoutHandlers[2].applicationEventPublisher != null
269+
270+
}
271+
@EnableWebSecurity
272+
static class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {
273+
}
274+
254275
}

config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616
package org.springframework.security.config.annotation.web.configurers
1717

1818
import groovy.transform.CompileStatic
19+
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler
1920

2021
import javax.servlet.ServletException
2122
import javax.servlet.ServletRequest
@@ -43,6 +44,7 @@ import org.springframework.security.web.servletapi.SecurityContextHolderAwareReq
4344
/**
4445
*
4546
* @author Rob Winch
47+
* @author Kazuki Shimizu
4648
*/
4749
class ServletApiConfigurerTests extends BaseSpringSpec {
4850

@@ -71,7 +73,7 @@ class ServletApiConfigurerTests extends BaseSpringSpec {
7173
and: "requestFactory != null"
7274
filter.requestFactory != null
7375
and: "logoutHandlers populated"
74-
filter.logoutHandlers.collect { it.class } == [CsrfLogoutHandler, SecurityContextLogoutHandler]
76+
filter.logoutHandlers.collect { it.class } == [CsrfLogoutHandler, SecurityContextLogoutHandler, LogoutSuccessEventPublishingLogoutHandler]
7577
}
7678

7779

config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.groovy

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,11 +15,13 @@
1515
*/
1616
package org.springframework.security.config.annotation.web.configurers
1717

18+
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler
19+
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler
20+
import org.springframework.security.web.csrf.CsrfLogoutHandler
21+
1822
import javax.servlet.http.HttpServletResponse
1923

2024
import org.springframework.mock.web.MockFilterChain
21-
import org.springframework.mock.web.MockHttpServletRequest
22-
import org.springframework.mock.web.MockHttpServletResponse
2325
import org.springframework.security.authentication.AuthenticationTrustResolver
2426
import org.springframework.security.config.annotation.AnyObjectPostProcessor
2527
import org.springframework.security.config.annotation.BaseSpringSpec
@@ -44,6 +46,7 @@ import org.springframework.security.web.session.SessionManagementFilter
4446
/**
4547
*
4648
* @author Rob Winch
49+
* @author Kazuki Shimizu
4750
*/
4851
class SessionManagementConfigurerTests extends BaseSpringSpec {
4952

@@ -250,4 +253,26 @@ class SessionManagementConfigurerTests extends BaseSpringSpec {
250253
.setSharedObject(AuthenticationTrustResolver, TR)
251254
}
252255
}
256+
257+
def "logout handler by default configuration"() {
258+
when:
259+
loadConfig(ConcurrencyWebSecurityConfig)
260+
then:
261+
def concurrentSessionFilter = findFilter(ConcurrentSessionFilter)
262+
concurrentSessionFilter.handlers.logoutHandlers.size() == 3
263+
concurrentSessionFilter.handlers.logoutHandlers[0].class == CsrfLogoutHandler
264+
concurrentSessionFilter.handlers.logoutHandlers[1].class == SecurityContextLogoutHandler
265+
concurrentSessionFilter.handlers.logoutHandlers[2].class == LogoutSuccessEventPublishingLogoutHandler
266+
concurrentSessionFilter.handlers.logoutHandlers[2].applicationEventPublisher != null
267+
}
268+
269+
@EnableWebSecurity
270+
static class ConcurrencyWebSecurityConfig extends WebSecurityConfigurerAdapter {
271+
@Override
272+
protected void configure(HttpSecurity http) throws Exception {
273+
super.configure(http)
274+
http.sessionManagement().maximumSessions(1)
275+
}
276+
}
277+
253278
}

config/src/test/groovy/org/springframework/security/config/http/RememberMeConfigTests.groovy

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.security.config.http
1717

18+
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler
19+
1820
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML
1921

2022
import javax.sql.DataSource
@@ -42,6 +44,7 @@ import org.springframework.security.web.authentication.rememberme.TokenBasedReme
4244
* @author Luke Taylor
4345
* @author Rob Winch
4446
* @author Oliver Becker
47+
* @author Kazuki Shimizu
4548
*/
4649
class RememberMeConfigTests extends AbstractHttpConfigTests {
4750

@@ -110,8 +113,11 @@ class RememberMeConfigTests extends AbstractHttpConfigTests {
110113
rmp != null
111114
5000 == FieldUtils.getFieldValue(rememberMeServices(), "tokenValiditySeconds")
112115
// SEC-909
113-
logoutHandlers.size() == 2
116+
logoutHandlers.size() == 3
117+
logoutHandlers.get(0) instanceof SecurityContextLogoutHandler
114118
logoutHandlers.get(1) == rememberMeServices()
119+
logoutHandlers.get(2) instanceof LogoutSuccessEventPublishingLogoutHandler
120+
logoutHandlers.get(2).applicationEventPublisher != null
115121
// SEC-1281
116122
rmp.key == "ourkey"
117123
}
@@ -128,9 +134,11 @@ class RememberMeConfigTests extends AbstractHttpConfigTests {
128134

129135
expect:
130136
rememberMeServices
131-
logoutHandlers.size() == 2
137+
logoutHandlers.size() == 3
132138
logoutHandlers.get(0) instanceof SecurityContextLogoutHandler
133139
logoutHandlers.get(1) == rememberMeServices
140+
logoutHandlers.get(2) instanceof LogoutSuccessEventPublishingLogoutHandler
141+
logoutHandlers.get(2).applicationEventPublisher != null
134142
}
135143

136144
def rememberMeTokenValidityIsParsedCorrectly() {

config/src/test/groovy/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.groovy

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,15 +15,13 @@
1515
*/
1616
package org.springframework.security.config.http
1717

18-
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML
18+
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler
1919

20-
import java.io.IOException;
20+
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML
2121

2222
import javax.servlet.ServletException;
2323
import javax.servlet.ServletRequest;
2424
import javax.servlet.ServletResponse;
25-
import javax.servlet.http.HttpServletRequest
26-
import javax.servlet.http.HttpServletResponse
2725

2826
import org.springframework.mock.web.MockFilterChain
2927
import org.springframework.mock.web.MockHttpServletRequest
@@ -43,6 +41,7 @@ import org.springframework.security.web.servletapi.SecurityContextHolderAwareReq
4341
/**
4442
*
4543
* @author Rob Winch
44+
* @author Kazuki Shimizu
4645
*/
4746
class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTests {
4847

@@ -57,8 +56,10 @@ class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTes
5756
expect:
5857
securityContextAwareFilter.authenticationEntryPoint.loginFormUrl == getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl
5958
securityContextAwareFilter.authenticationManager == getFilter(UsernamePasswordAuthenticationFilter).authenticationManager
60-
securityContextAwareFilter.logoutHandlers.size() == 1
59+
securityContextAwareFilter.logoutHandlers.size() == 2
6160
securityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler
61+
securityContextAwareFilter.logoutHandlers[1].class == LogoutSuccessEventPublishingLogoutHandler
62+
securityContextAwareFilter.logoutHandlers[1].applicationEventPublisher != null
6263
}
6364

6465
def explicitEntryPoint() {
@@ -112,16 +113,20 @@ class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTes
112113
securityContextAwareFilter.authenticationEntryPoint.loginFormUrl == '/login'
113114
securityContextAwareFilter.authenticationManager == getFilters('/first/filters').find { it instanceof UsernamePasswordAuthenticationFilter}.authenticationManager
114115
securityContextAwareFilter.authenticationManager.parent == appContext.getBean('authManager')
115-
securityContextAwareFilter.logoutHandlers.size() == 1
116+
securityContextAwareFilter.logoutHandlers.size() == 2
116117
securityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler
117118
securityContextAwareFilter.logoutHandlers[0].invalidateHttpSession == true
119+
securityContextAwareFilter.logoutHandlers[1].class == LogoutSuccessEventPublishingLogoutHandler
120+
securityContextAwareFilter.logoutHandlers[1].applicationEventPublisher != null
118121

119122
secondSecurityContextAwareFilter.authenticationEntryPoint.loginFormUrl == '/login2'
120123
secondSecurityContextAwareFilter.authenticationManager == getFilter(UsernamePasswordAuthenticationFilter).authenticationManager
121124
secondSecurityContextAwareFilter.authenticationManager.parent == appContext.getBean('authManager2')
122-
securityContextAwareFilter.logoutHandlers.size() == 1
125+
secondSecurityContextAwareFilter.logoutHandlers.size() == 2
123126
secondSecurityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler
124127
secondSecurityContextAwareFilter.logoutHandlers[0].invalidateHttpSession == false
128+
secondSecurityContextAwareFilter.logoutHandlers[1].class == LogoutSuccessEventPublishingLogoutHandler
129+
secondSecurityContextAwareFilter.logoutHandlers[1].applicationEventPublisher != null
125130
}
126131

127132
def logoutCustom() {
@@ -137,11 +142,13 @@ class SecurityContextHolderAwareRequestConfigTests extends AbstractHttpConfigTes
137142
expect:
138143
securityContextAwareFilter.authenticationEntryPoint.loginFormUrl == getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl
139144
securityContextAwareFilter.authenticationManager == getFilter(UsernamePasswordAuthenticationFilter).authenticationManager
140-
securityContextAwareFilter.logoutHandlers.size() == 2
145+
securityContextAwareFilter.logoutHandlers.size() == 3
141146
securityContextAwareFilter.logoutHandlers[0].class == SecurityContextLogoutHandler
142147
securityContextAwareFilter.logoutHandlers[0].invalidateHttpSession == false
143148
securityContextAwareFilter.logoutHandlers[1].class == CookieClearingLogoutHandler
144149
securityContextAwareFilter.logoutHandlers[1].cookiesToClear == ['JSESSIONID']
150+
securityContextAwareFilter.logoutHandlers[2].class == LogoutSuccessEventPublishingLogoutHandler
151+
securityContextAwareFilter.logoutHandlers[2].applicationEventPublisher != null
145152
}
146153

147154
def 'SEC-2926: Role Prefix is set'() {

0 commit comments

Comments
 (0)