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