diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionEvent.java b/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionEvent.java new file mode 100644 index 0000000000..085f6d18a7 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionEvent.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2013 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.session; + +import org.springframework.security.authentication.event.AbstractAuthenticationEvent; +import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; + +/** + * Indicates a session ID was changed for the purposes of session fixation protection. + * + * @author Nicholas Williams + * @since 3.2 + * @see SessionFixationProtectionStrategy + */ +public class SessionFixationProtectionEvent extends AbstractAuthenticationEvent { + //~ Instance fields ================================================================================================ + + private final String oldSessionId; + + private final String newSessionId; + + //~ Constructors =================================================================================================== + + /** + * Constructs a new session fixation protection event. + * + * @param authentication The authentication object + * @param oldSessionId The old session ID before it was changed + * @param newSessionId The new session ID after it was changed + */ + public SessionFixationProtectionEvent(Authentication authentication, String oldSessionId, String newSessionId) { + super(authentication); + Assert.hasLength(oldSessionId); + Assert.hasLength(newSessionId); + this.oldSessionId = oldSessionId; + this.newSessionId = newSessionId; + } + + //~ Methods ======================================================================================================== + + /** + * Getter for the session ID before it was changed. + * + * @return the old session ID. + */ + public String getOldSessionId() { + return this.oldSessionId; + } + + /** + * Getter for the session ID after it was changed. + * + * @return the new session ID. + */ + public String getNewSessionId() { + return this.newSessionId; + } +} diff --git a/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionStrategy.java b/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionStrategy.java index 9532645212..eab586e9aa 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionStrategy.java +++ b/web/src/main/java/org/springframework/security/web/authentication/session/SessionFixationProtectionStrategy.java @@ -1,14 +1,37 @@ +/* + * Copyright 2002-2013 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.session; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import java.util.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; /** * The default implementation of {@link SessionAuthenticationStrategy}. @@ -36,9 +59,14 @@ import java.util.*; * @author Luke Taylor * @since 3.0 */ -public class SessionFixationProtectionStrategy implements SessionAuthenticationStrategy { +public class SessionFixationProtectionStrategy implements SessionAuthenticationStrategy, ApplicationEventPublisherAware { protected final Log logger = LogFactory.getLog(this.getClass()); + /** + * Used for publishing events related to session fixation protection, such as {@link SessionFixationProtectionEvent}. + */ + private ApplicationEventPublisher applicationEventPublisher = new NullEventPublisher(); + /** * Indicates that the session attributes of an existing session * should be migrated to the new session. Defaults to true. @@ -112,12 +140,19 @@ public class SessionFixationProtectionStrategy implements SessionAuthenticationS /** * Called when the session has been changed and the old attributes have been migrated to the new session. * Only called if a session existed to start with. Allows subclasses to plug in additional behaviour. + * *

+ * The default implementation of this method publishes a {@link SessionFixationProtectionEvent} to notify + * the application that the session ID has changed. If you override this method and still wish these events to be + * published, you should call {@code super.onSessionChange()} within your overriding method. * * @param originalSessionId the original session identifier * @param newSession the newly created session * @param auth the token for the newly authenticated principal */ protected void onSessionChange(String originalSessionId, HttpSession newSession, Authentication auth) { + applicationEventPublisher.publishEvent(new SessionFixationProtectionEvent( + auth, originalSessionId, newSession.getId() + )); } /** @@ -179,6 +214,10 @@ public class SessionFixationProtectionStrategy implements SessionAuthenticationS return attributesToMigrate; } + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + /** * Defines whether attributes should be migrated to a new session or not. Has no effect if you * override the {@code extractAttributes} method. @@ -206,4 +245,8 @@ public class SessionFixationProtectionStrategy implements SessionAuthenticationS public void setAlwaysCreateSession(boolean alwaysCreateSession) { this.alwaysCreateSession = alwaysCreateSession; } + + private static final class NullEventPublisher implements ApplicationEventPublisher { + public void publishEvent(ApplicationEvent event) { } + } } diff --git a/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlStrategyTests.java b/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlStrategyTests.java index c997162fdb..2949d25c5a 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlStrategyTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlStrategyTests.java @@ -1,17 +1,36 @@ +/* + * Copyright 2002-2013 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.session; +import static org.junit.Assert.*; import static org.mockito.AdditionalMatchers.not; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.core.Authentication; @@ -60,4 +79,28 @@ public class ConcurrentSessionControlStrategyTests { verify(sessionRegistry,times(0)).removeSessionInformation(anyString()); verify(sessionRegistry).registerNewSession(not(eq(originalSessionId)), anyObject()); } + + // SEC-2002 + @Test + public void onAuthenticationChangeSessionWithEventPublisher() { + String originalSessionId = request.getSession().getId(); + + ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class); + strategy.setApplicationEventPublisher(eventPublisher); + + strategy.onAuthentication(authentication, request, response); + + verify(sessionRegistry,times(0)).removeSessionInformation(anyString()); + verify(sessionRegistry).registerNewSession(not(eq(originalSessionId)), anyObject()); + + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); + verify(eventPublisher).publishEvent(eventArgumentCaptor.capture()); + + assertNotNull(eventArgumentCaptor.getValue()); + assertTrue(eventArgumentCaptor.getValue() instanceof SessionFixationProtectionEvent); + SessionFixationProtectionEvent event = (SessionFixationProtectionEvent)eventArgumentCaptor.getValue(); + assertEquals(originalSessionId, event.getOldSessionId()); + assertEquals(request.getSession().getId(), event.getNewSessionId()); + assertSame(authentication, event.getAuthentication()); + } } diff --git a/web/src/test/java/org/springframework/security/web/session/DefaultSessionAuthenticationStrategyTests.java b/web/src/test/java/org/springframework/security/web/session/DefaultSessionAuthenticationStrategyTests.java index 63ccc73176..aaea5b0df9 100644 --- a/web/src/test/java/org/springframework/security/web/session/DefaultSessionAuthenticationStrategyTests.java +++ b/web/src/test/java/org/springframework/security/web/session/DefaultSessionAuthenticationStrategyTests.java @@ -1,17 +1,36 @@ +/* + * Copyright 2002-2013 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.session; import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.session.SessionFixationProtectionEvent; import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy; -import org.springframework.security.web.savedrequest.HttpSessionRequestCache; /** * @@ -40,6 +59,38 @@ public class DefaultSessionAuthenticationStrategyTests { assertFalse(sessionId.equals(request.getSession().getId())); } + // SEC-2002 + @Test + public void newSessionIsCreatedIfSessionAlreadyExistsWithEventPublisher() throws Exception { + SessionFixationProtectionStrategy strategy = new SessionFixationProtectionStrategy(); + HttpServletRequest request = new MockHttpServletRequest(); + HttpSession session = request.getSession(); + session.setAttribute("blah", "blah"); + session.setAttribute("SPRING_SECURITY_SAVED_REQUEST_KEY", "DefaultSavedRequest"); + String oldSessionId = session.getId(); + + ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class); + strategy.setApplicationEventPublisher(eventPublisher); + + Authentication mockAuthentication = mock(Authentication.class); + + strategy.onAuthentication(mockAuthentication, request, new MockHttpServletResponse()); + + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); + verify(eventPublisher).publishEvent(eventArgumentCaptor.capture()); + + assertFalse(oldSessionId.equals(request.getSession().getId())); + assertNotNull(request.getSession().getAttribute("blah")); + assertNotNull(request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST_KEY")); + + assertNotNull(eventArgumentCaptor.getValue()); + assertTrue(eventArgumentCaptor.getValue() instanceof SessionFixationProtectionEvent); + SessionFixationProtectionEvent event = (SessionFixationProtectionEvent)eventArgumentCaptor.getValue(); + assertEquals(oldSessionId, event.getOldSessionId()); + assertEquals(request.getSession().getId(), event.getNewSessionId()); + assertSame(mockAuthentication, event.getAuthentication()); + } + // See SEC-1077 @Test public void onlySavedRequestAttributeIsMigratedIfMigrateAttributesIsFalse() throws Exception { @@ -56,6 +107,38 @@ public class DefaultSessionAuthenticationStrategyTests { assertNotNull(request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST_KEY")); } + // SEC-2002 + @Test + public void onlySavedRequestAttributeIsMigratedIfMigrateAttributesIsFalseWithEventPublisher() throws Exception { + SessionFixationProtectionStrategy strategy = new SessionFixationProtectionStrategy(); + strategy.setMigrateSessionAttributes(false); + HttpServletRequest request = new MockHttpServletRequest(); + HttpSession session = request.getSession(); + session.setAttribute("blah", "blah"); + session.setAttribute("SPRING_SECURITY_SAVED_REQUEST_KEY", "DefaultSavedRequest"); + String oldSessionId = session.getId(); + + ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class); + strategy.setApplicationEventPublisher(eventPublisher); + + Authentication mockAuthentication = mock(Authentication.class); + + strategy.onAuthentication(mockAuthentication, request, new MockHttpServletResponse()); + + ArgumentCaptor eventArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); + verify(eventPublisher).publishEvent(eventArgumentCaptor.capture()); + + assertNull(request.getSession().getAttribute("blah")); + assertNotNull(request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST_KEY")); + + assertNotNull(eventArgumentCaptor.getValue()); + assertTrue(eventArgumentCaptor.getValue() instanceof SessionFixationProtectionEvent); + SessionFixationProtectionEvent event = (SessionFixationProtectionEvent)eventArgumentCaptor.getValue(); + assertEquals(oldSessionId, event.getOldSessionId()); + assertEquals(request.getSession().getId(), event.getNewSessionId()); + assertSame(mockAuthentication, event.getAuthentication()); + } + @Test public void sessionIsCreatedIfAlwaysCreateTrue() throws Exception { SessionFixationProtectionStrategy strategy = new SessionFixationProtectionStrategy();