Introduce LogoutSuccessEvent

LogoutSuccessEvent is a simple AbstractAuthenticationEvent implementation which indicates successful logout.

By default, LogoutConfigurer will add a new LogoutHandler called LogoutSuccessEventPublishingLogoutHandler to publish this event.

This PR will also fix ConcurrentSessionFilter's composite logoutHandler, now will get LogoutHandler instances from LogoutConfigurer for consistency.

Fixes gh-2900
This commit is contained in:
Onur Kagan Ozcan
2019-08-23 19:01:24 +03:00
committed by Rob Winch
parent 7576dc44d7
commit 034b5e9e93
10 changed files with 291 additions and 13 deletions

View File

@@ -16,6 +16,8 @@
package org.springframework.security.config.annotation.web.configurers;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,12 +35,19 @@ import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.util.FieldUtils;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.authentication.logout.CompositeLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -59,6 +68,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Rob Winch
* @author Eleftheria Stein
* @author Onur Kagan Ozcan
*/
public class ServletApiConfigurerTests {
@Rule
@@ -287,4 +297,56 @@ public class ServletApiConfigurerTests {
}
}
}
@Test
public void checkSecurityContextAwareAndLogoutFilterHasSameSizeAndHasLogoutSuccessEventPublishingLogoutHandler() {
this.spring.register(ServletApiWithLogoutConfig.class);
SecurityContextHolderAwareRequestFilter scaFilter = getFilter(SecurityContextHolderAwareRequestFilter.class);
LogoutFilter logoutFilter = getFilter(LogoutFilter.class);
LogoutHandler lfLogoutHandler = getFieldValue(logoutFilter, "handler");
assertThat(lfLogoutHandler).isInstanceOf(CompositeLogoutHandler.class);
List<LogoutHandler> scaLogoutHandlers = getFieldValue(scaFilter, "logoutHandlers");
List<LogoutHandler> lfLogoutHandlers = getFieldValue(lfLogoutHandler, "logoutHandlers");
assertThat(scaLogoutHandlers).hasSameSizeAs(lfLogoutHandlers);
assertThat(scaLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class);
assertThat(lfLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class);
}
@EnableWebSecurity
static class ServletApiWithLogoutConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.servletApi().and()
.logout();
// @formatter:on
}
}
private <T extends Filter> T getFilter(Class<T> filterClass) {
return (T) getFilters().stream()
.filter(filterClass::isInstance)
.findFirst()
.orElse(null);
}
private List<Filter> getFilters() {
FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class);
return proxy.getFilters("/");
}
private <T> T getFieldValue(Object target, String fieldName) {
try {
return (T) FieldUtils.getFieldValue(target, fieldName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -41,7 +41,10 @@ import org.springframework.security.util.FieldUtils;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.CompositeLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.session.ConcurrentSessionFilter;
@@ -71,6 +74,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Luke Taylor
* @author Rob Winch
* @author Josh Cummings
* @author Onur Kagan Ozcan
*/
public class SessionManagementConfigTests {
private static final String CONFIG_LOCATION_PREFIX =
@@ -455,6 +459,32 @@ public class SessionManagementConfigTests {
.andExpect(redirectedUrl("/timeoutUrl"));
}
/**
* SEC-2680
*/
@Test
public void checkConcurrencyAndLogoutFilterHasSameSizeAndHasLogoutSuccessEventPublishingLogoutHandler() {
this.spring.configLocations(this.xml("ConcurrencyControlLogoutAndRememberMeHandlers")).autowire();
ConcurrentSessionFilter concurrentSessionFilter = getFilter(ConcurrentSessionFilter.class);
LogoutFilter logoutFilter = getFilter(LogoutFilter.class);
LogoutHandler csfLogoutHandler = getFieldValue(concurrentSessionFilter, "handlers");
LogoutHandler lfLogoutHandler = getFieldValue(logoutFilter, "handler");
assertThat(csfLogoutHandler).isInstanceOf(CompositeLogoutHandler.class);
assertThat(lfLogoutHandler).isInstanceOf(CompositeLogoutHandler.class);
List<LogoutHandler> csfLogoutHandlers = getFieldValue(csfLogoutHandler, "logoutHandlers");
List<LogoutHandler> lfLogoutHandlers = getFieldValue(lfLogoutHandler, "logoutHandlers");
assertThat(csfLogoutHandlers).hasSameSizeAs(lfLogoutHandlers);
assertThat(csfLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class);
assertThat(lfLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class);
}
static class TeapotSessionAuthenticationStrategy implements SessionAuthenticationStrategy {
@Override