2
0
Эх сурвалжийг харах

SEC-2574: JavaConfig default SessionRegistry processes SessionDestroyedEvents

Rob Winch 10 жил өмнө
parent
commit
b71989ecde

+ 6 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java

@@ -40,6 +40,7 @@ import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.SecurityConfigurer;
 import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
 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.FilterInvocation;
 import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
@@ -72,6 +73,11 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
 
     private ClassLoader beanClassLoader;
 
+    @Bean
+    public DelegatingApplicationListener delegatingApplicationListener() {
+        return new DelegatingApplicationListener();
+    }
+
     @Bean
     @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
     public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {

+ 33 - 5
config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java

@@ -22,10 +22,15 @@ import java.util.List;
 import javax.servlet.http.HttpServletResponse;
 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.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 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.SessionRegistryImpl;
 import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
@@ -87,7 +92,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
     private SessionAuthenticationStrategy sessionAuthenticationStrategy;
     private InvalidSessionStrategy invalidSessionStrategy;
     private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<SessionAuthenticationStrategy>();
-    private SessionRegistry sessionRegistry = new SessionRegistryImpl();
+    private SessionRegistry sessionRegistry;
     private Integer maximumSessions;
     private String expiredUrl;
     private boolean maxSessionsPreventsLogin;
@@ -367,14 +372,14 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
                 http.setSharedObject(RequestCache.class, new NullRequestCache());
             }
         }
-        http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy());
+        http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http));
         http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
     }
 
     @Override
     public void configure(H http) throws Exception {
         SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
-        SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository, getSessionAuthenticationStrategy());
+        SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository, getSessionAuthenticationStrategy(http));
         if(sessionAuthenticationErrorUrl != null) {
             sessionManagementFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(sessionAuthenticationErrorUrl));
         }
@@ -389,7 +394,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
 
         http.addFilter(sessionManagementFilter);
         if(isConcurrentSessionControlEnabled()) {
-            ConcurrentSessionFilter concurrentSessionFilter = new ConcurrentSessionFilter(sessionRegistry, expiredUrl);
+            ConcurrentSessionFilter concurrentSessionFilter = new ConcurrentSessionFilter(getSessionRegistry(http), expiredUrl);
             concurrentSessionFilter = postProcess(concurrentSessionFilter);
             http.addFilter(concurrentSessionFilter);
         }
@@ -444,12 +449,13 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
      *
      * @return the {@link SessionAuthenticationStrategy} to use
      */
-    private SessionAuthenticationStrategy getSessionAuthenticationStrategy() {
+    private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) {
         if(sessionAuthenticationStrategy != null) {
             return sessionAuthenticationStrategy;
         }
         List<SessionAuthenticationStrategy> delegateStrategies = sessionAuthenticationStrategies;
         if(isConcurrentSessionControlEnabled()) {
+            SessionRegistry sessionRegistry = getSessionRegistry(http);
             ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
             concurrentSessionControlStrategy.setMaximumSessions(maximumSessions);
             concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(maxSessionsPreventsLogin);
@@ -466,6 +472,28 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
         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.
      * @return

+ 15 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.groovy

@@ -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.WebSecurityConfigurerAdapter
 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.authentication.session.CompositeSessionAuthenticationStrategy
 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.savedrequest.RequestCache
 import org.springframework.security.web.session.ConcurrentSessionFilter
+import org.springframework.security.web.session.HttpSessionDestroyedEvent;
 import org.springframework.security.web.session.SessionManagementFilter
 
 /**
@@ -154,12 +156,14 @@ class SessionManagementConfigurerTests extends BaseSpringSpec {
     def 'session fixation and enable concurrency control'() {
         setup: "context where session fixation is disabled and concurrency control is enabled"
             loadConfig(ConcurrencyControlConfig)
+            def authenticatedSession
         when: "authenticate successfully"
             request.servletPath = "/login"
             request.method = "POST"
             request.setParameter("username", "user");
             request.setParameter("password","password")
             springSecurityFilterChain.doFilter(request, response, chain)
+            authenticatedSession = request.session
         then: "authentication is sucessful"
             response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
             response.redirectedUrl == "/"
@@ -173,6 +177,17 @@ class SessionManagementConfigurerTests extends BaseSpringSpec {
         then:
             response.status == HttpServletResponse.SC_MOVED_TEMPORARILY
             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

+ 57 - 0
core/src/main/java/org/springframework/security/context/DelegatingApplicationListener.java

@@ -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);
+    }
+}

+ 72 - 0
core/src/test/java/org/springframework/security/context/DelegatingApplicationListenerTests.java

@@ -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);
+    }
+
+}