mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-08 03:32:39 +00:00
SEC-2574: JavaConfig default SessionRegistry processes SessionDestroyedEvents
This commit is contained in:
parent
eeef91498a
commit
b71989ecde
@ -40,6 +40,7 @@ import org.springframework.security.config.annotation.ObjectPostProcessor;
|
|||||||
import org.springframework.security.config.annotation.SecurityConfigurer;
|
import org.springframework.security.config.annotation.SecurityConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
|
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||||
|
import org.springframework.security.context.DelegatingApplicationListener;
|
||||||
import org.springframework.security.web.FilterChainProxy;
|
import org.springframework.security.web.FilterChainProxy;
|
||||||
import org.springframework.security.web.FilterInvocation;
|
import org.springframework.security.web.FilterInvocation;
|
||||||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
||||||
@ -72,6 +73,11 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
|
|||||||
|
|
||||||
private ClassLoader beanClassLoader;
|
private ClassLoader beanClassLoader;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DelegatingApplicationListener delegatingApplicationListener() {
|
||||||
|
return new DelegatingApplicationListener();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
|
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
|
||||||
public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
|
public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
|
||||||
|
@ -22,10 +22,15 @@ import java.util.List;
|
|||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.event.GenericApplicationListenerAdapter;
|
||||||
|
import org.springframework.context.event.SmartApplicationListener;
|
||||||
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.context.DelegatingApplicationListener;
|
||||||
import org.springframework.security.core.session.SessionRegistry;
|
import org.springframework.security.core.session.SessionRegistry;
|
||||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
@ -87,7 +92,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
|
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
|
||||||
private InvalidSessionStrategy invalidSessionStrategy;
|
private InvalidSessionStrategy invalidSessionStrategy;
|
||||||
private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<SessionAuthenticationStrategy>();
|
private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<SessionAuthenticationStrategy>();
|
||||||
private SessionRegistry sessionRegistry = new SessionRegistryImpl();
|
private SessionRegistry sessionRegistry;
|
||||||
private Integer maximumSessions;
|
private Integer maximumSessions;
|
||||||
private String expiredUrl;
|
private String expiredUrl;
|
||||||
private boolean maxSessionsPreventsLogin;
|
private boolean maxSessionsPreventsLogin;
|
||||||
@ -367,14 +372,14 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
http.setSharedObject(RequestCache.class, new NullRequestCache());
|
http.setSharedObject(RequestCache.class, new NullRequestCache());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy());
|
http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http));
|
||||||
http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
|
http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(H http) throws Exception {
|
public void configure(H http) throws Exception {
|
||||||
SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
|
SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
|
||||||
SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository, getSessionAuthenticationStrategy());
|
SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository, getSessionAuthenticationStrategy(http));
|
||||||
if(sessionAuthenticationErrorUrl != null) {
|
if(sessionAuthenticationErrorUrl != null) {
|
||||||
sessionManagementFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(sessionAuthenticationErrorUrl));
|
sessionManagementFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(sessionAuthenticationErrorUrl));
|
||||||
}
|
}
|
||||||
@ -389,7 +394,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
|
|
||||||
http.addFilter(sessionManagementFilter);
|
http.addFilter(sessionManagementFilter);
|
||||||
if(isConcurrentSessionControlEnabled()) {
|
if(isConcurrentSessionControlEnabled()) {
|
||||||
ConcurrentSessionFilter concurrentSessionFilter = new ConcurrentSessionFilter(sessionRegistry, expiredUrl);
|
ConcurrentSessionFilter concurrentSessionFilter = new ConcurrentSessionFilter(getSessionRegistry(http), expiredUrl);
|
||||||
concurrentSessionFilter = postProcess(concurrentSessionFilter);
|
concurrentSessionFilter = postProcess(concurrentSessionFilter);
|
||||||
http.addFilter(concurrentSessionFilter);
|
http.addFilter(concurrentSessionFilter);
|
||||||
}
|
}
|
||||||
@ -444,12 +449,13 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
*
|
*
|
||||||
* @return the {@link SessionAuthenticationStrategy} to use
|
* @return the {@link SessionAuthenticationStrategy} to use
|
||||||
*/
|
*/
|
||||||
private SessionAuthenticationStrategy getSessionAuthenticationStrategy() {
|
private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) {
|
||||||
if(sessionAuthenticationStrategy != null) {
|
if(sessionAuthenticationStrategy != null) {
|
||||||
return sessionAuthenticationStrategy;
|
return sessionAuthenticationStrategy;
|
||||||
}
|
}
|
||||||
List<SessionAuthenticationStrategy> delegateStrategies = sessionAuthenticationStrategies;
|
List<SessionAuthenticationStrategy> delegateStrategies = sessionAuthenticationStrategies;
|
||||||
if(isConcurrentSessionControlEnabled()) {
|
if(isConcurrentSessionControlEnabled()) {
|
||||||
|
SessionRegistry sessionRegistry = getSessionRegistry(http);
|
||||||
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
|
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
|
||||||
concurrentSessionControlStrategy.setMaximumSessions(maximumSessions);
|
concurrentSessionControlStrategy.setMaximumSessions(maximumSessions);
|
||||||
concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(maxSessionsPreventsLogin);
|
concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(maxSessionsPreventsLogin);
|
||||||
@ -466,6 +472,28 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
return sessionAuthenticationStrategy;
|
return sessionAuthenticationStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SessionRegistry getSessionRegistry(H http) {
|
||||||
|
if(sessionRegistry == null) {
|
||||||
|
SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
|
||||||
|
registerDelegateApplicationListener(http, sessionRegistry);
|
||||||
|
this.sessionRegistry = sessionRegistry;
|
||||||
|
}
|
||||||
|
return sessionRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerDelegateApplicationListener(H http, ApplicationListener<?> delegate) {
|
||||||
|
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
|
||||||
|
if(context == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(context.getBeansOfType(DelegatingApplicationListener.class).isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DelegatingApplicationListener delegating = context.getBean(DelegatingApplicationListener.class);
|
||||||
|
SmartApplicationListener smartListener = new GenericApplicationListenerAdapter(delegate);
|
||||||
|
delegating.addListener(smartListener);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the number of concurrent sessions per user should be restricted.
|
* Returns true if the number of concurrent sessions per user should be restricted.
|
||||||
* @return
|
* @return
|
||||||
|
@ -29,6 +29,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
|||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy
|
import org.springframework.security.config.http.SessionCreationPolicy
|
||||||
|
import org.springframework.security.core.session.SessionDestroyedEvent
|
||||||
import org.springframework.security.web.access.ExceptionTranslationFilter
|
import org.springframework.security.web.access.ExceptionTranslationFilter
|
||||||
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy
|
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy
|
||||||
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy
|
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy
|
||||||
@ -38,6 +39,7 @@ import org.springframework.security.web.context.SecurityContextPersistenceFilter
|
|||||||
import org.springframework.security.web.context.SecurityContextRepository
|
import org.springframework.security.web.context.SecurityContextRepository
|
||||||
import org.springframework.security.web.savedrequest.RequestCache
|
import org.springframework.security.web.savedrequest.RequestCache
|
||||||
import org.springframework.security.web.session.ConcurrentSessionFilter
|
import org.springframework.security.web.session.ConcurrentSessionFilter
|
||||||
|
import org.springframework.security.web.session.HttpSessionDestroyedEvent;
|
||||||
import org.springframework.security.web.session.SessionManagementFilter
|
import org.springframework.security.web.session.SessionManagementFilter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,12 +156,14 @@ class SessionManagementConfigurerTests extends BaseSpringSpec {
|
|||||||
def 'session fixation and enable concurrency control'() {
|
def 'session fixation and enable concurrency control'() {
|
||||||
setup: "context where session fixation is disabled and concurrency control is enabled"
|
setup: "context where session fixation is disabled and concurrency control is enabled"
|
||||||
loadConfig(ConcurrencyControlConfig)
|
loadConfig(ConcurrencyControlConfig)
|
||||||
|
def authenticatedSession
|
||||||
when: "authenticate successfully"
|
when: "authenticate successfully"
|
||||||
request.servletPath = "/login"
|
request.servletPath = "/login"
|
||||||
request.method = "POST"
|
request.method = "POST"
|
||||||
request.setParameter("username", "user");
|
request.setParameter("username", "user");
|
||||||
request.setParameter("password","password")
|
request.setParameter("password","password")
|
||||||
springSecurityFilterChain.doFilter(request, response, chain)
|
springSecurityFilterChain.doFilter(request, response, chain)
|
||||||
|
authenticatedSession = request.session
|
||||||
then: "authentication is sucessful"
|
then: "authentication is sucessful"
|
||||||
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
||||||
response.redirectedUrl == "/"
|
response.redirectedUrl == "/"
|
||||||
@ -173,6 +177,17 @@ class SessionManagementConfigurerTests extends BaseSpringSpec {
|
|||||||
then:
|
then:
|
||||||
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
||||||
response.redirectedUrl == '/login?error'
|
response.redirectedUrl == '/login?error'
|
||||||
|
when: 'SEC-2574: When Session Expires and authentication attempted'
|
||||||
|
context.publishEvent(new HttpSessionDestroyedEvent(authenticatedSession))
|
||||||
|
super.setup()
|
||||||
|
request.servletPath = "/login"
|
||||||
|
request.method = "POST"
|
||||||
|
request.setParameter("username", "user");
|
||||||
|
request.setParameter("password","password")
|
||||||
|
springSecurityFilterChain.doFilter(request, response, chain)
|
||||||
|
then: "authentication is successful"
|
||||||
|
response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
|
||||||
|
response.redirectedUrl == "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2014 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.context;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.event.SmartApplicationListener;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for delegating to a number of SmartApplicationListener instances. This is useful when needing to register an
|
||||||
|
* SmartApplicationListener with the ApplicationContext programmatically.
|
||||||
|
*
|
||||||
|
* @author Rob Winch
|
||||||
|
*/
|
||||||
|
public final class DelegatingApplicationListener implements ApplicationListener<ApplicationEvent> {
|
||||||
|
private List<SmartApplicationListener> listeners = new ArrayList<SmartApplicationListener>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ApplicationEvent event) {
|
||||||
|
if(event == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(SmartApplicationListener listener : listeners) {
|
||||||
|
Object source = event.getSource();
|
||||||
|
if(source != null && listener.supportsEventType(event.getClass()) && listener.supportsSourceType(source.getClass())) {
|
||||||
|
listener.onApplicationEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new SmartApplicationListener to use.
|
||||||
|
*
|
||||||
|
* @param smartApplicationListener the SmartApplicationListener to use. Cannot be null.
|
||||||
|
*/
|
||||||
|
public void addListener(SmartApplicationListener smartApplicationListener) {
|
||||||
|
Assert.notNull(smartApplicationListener, "smartApplicationListener cannot be null");
|
||||||
|
listeners.add(smartApplicationListener);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package org.springframework.security.context;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.event.SmartApplicationListener;
|
||||||
|
import org.springframework.security.core.session.SessionDestroyedEvent;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class DelegatingApplicationListenerTests {
|
||||||
|
@Mock
|
||||||
|
SmartApplicationListener delegate;
|
||||||
|
|
||||||
|
ApplicationEvent event;
|
||||||
|
|
||||||
|
DelegatingApplicationListener listener;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
event = new ApplicationEvent(this) {};
|
||||||
|
listener = new DelegatingApplicationListener();
|
||||||
|
listener.addListener(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processEventNull() {
|
||||||
|
listener.onApplicationEvent(null);
|
||||||
|
|
||||||
|
verify(delegate,never()).onApplicationEvent(any(ApplicationEvent.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processEventSuccess() {
|
||||||
|
when(delegate.supportsEventType(event.getClass())).thenReturn(true);
|
||||||
|
when(delegate.supportsSourceType(event.getSource().getClass())).thenReturn(true);
|
||||||
|
listener.onApplicationEvent(event);
|
||||||
|
|
||||||
|
verify(delegate).onApplicationEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processEventEventTypeNotSupported() {
|
||||||
|
when(delegate.supportsSourceType(event.getSource().getClass())).thenReturn(true);
|
||||||
|
listener.onApplicationEvent(event);
|
||||||
|
|
||||||
|
verify(delegate,never()).onApplicationEvent(any(ApplicationEvent.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processEventSourceTypeNotSupported() {
|
||||||
|
when(delegate.supportsEventType(event.getClass())).thenReturn(true);
|
||||||
|
listener.onApplicationEvent(event);
|
||||||
|
|
||||||
|
verify(delegate,never()).onApplicationEvent(any(ApplicationEvent.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void addNull() {
|
||||||
|
listener.addListener(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user