Skip to content

SEC-2002: Added events to notify of session ID change #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 6, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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}.
Expand Down Expand Up @@ -36,9 +59,14 @@
* @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 <code>true</code>.
Expand Down Expand Up @@ -112,12 +140,19 @@ public void onAuthentication(Authentication authentication, HttpServletRequest r
/**
* 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.
* * <p>
* 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()
));
}

/**
Expand Down Expand Up @@ -179,6 +214,10 @@ private HashMap<String, Object> createMigratedAttributeMap(HttpSession session)
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.
Expand Down Expand Up @@ -206,4 +245,8 @@ public void setRetainedAttributes(List<String> retainedAttributes) {
public void setAlwaysCreateSession(boolean alwaysCreateSession) {
this.alwaysCreateSession = alwaysCreateSession;
}

private static final class NullEventPublisher implements ApplicationEventPublisher {
public void publishEvent(ApplicationEvent event) { }
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -60,4 +79,28 @@ public void onAuthenticationChangeSession() {
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<ApplicationEvent> 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());
}
}
Original file line number Diff line number Diff line change
@@ -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;

/**
*
Expand Down Expand Up @@ -40,6 +59,38 @@ public void newSessionIsCreatedIfSessionAlreadyExists() throws Exception {
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<ApplicationEvent> 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 {
Expand All @@ -56,6 +107,38 @@ public void onlySavedRequestAttributeIsMigratedIfMigrateAttributesIsFalse() thro
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<ApplicationEvent> 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();
Expand Down