SEC-2135: Support HttpServletRequest#changeSessionId()
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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 javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* A base class for performing session fixation protection.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
abstract class AbstractSessionFixationProtectionStrategy 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();
|
||||
/**
|
||||
* If set to {@code true}, a session will always be created, even if one didn't exist at the start of the request.
|
||||
* Defaults to {@code false}.
|
||||
*/
|
||||
private boolean alwaysCreateSession;
|
||||
|
||||
/**
|
||||
* Called when a user is newly authenticated.
|
||||
* <p>
|
||||
* If a session already exists, and matches the session Id from the client, a new session will be created, and the
|
||||
* session attributes copied to it (if {@code migrateSessionAttributes} is set).
|
||||
* If the client's requested session Id is invalid, nothing will be done, since there is no need to change the
|
||||
* session Id if it doesn't match the current session.
|
||||
* <p>
|
||||
* If there is no session, no action is taken unless the {@code alwaysCreateSession} property is set, in which
|
||||
* case a session will be created if one doesn't already exist.
|
||||
*/
|
||||
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
|
||||
boolean hadSessionAlready = request.getSession(false) != null;
|
||||
|
||||
if (!hadSessionAlready && !alwaysCreateSession) {
|
||||
// Session fixation isn't a problem if there's no session
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new session if necessary
|
||||
HttpSession session = request.getSession();
|
||||
|
||||
if (hadSessionAlready && request.isRequestedSessionIdValid()) {
|
||||
// We need to migrate to a new session
|
||||
String originalSessionId = session.getId();
|
||||
|
||||
session = applySessionFixation(request);
|
||||
|
||||
if (originalSessionId.equals(session.getId())) {
|
||||
logger.warn("Your servlet container did not change the session ID when a new session was created. You will" +
|
||||
" not be adequately protected against session-fixation attacks");
|
||||
}
|
||||
|
||||
onSessionChange(originalSessionId, session, authentication);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies session fixation
|
||||
*
|
||||
* @param request the {@link HttpServletRequest} to apply session fixation protection for
|
||||
* @return the new {@link HttpSession} to use. Cannot be null.
|
||||
*/
|
||||
abstract HttpSession applySessionFixation(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 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()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ApplicationEventPublisher} to use for submitting
|
||||
* {@link SessionFixationProtectionEvent}. The default is to not submit the
|
||||
* {@link SessionFixationProtectionEvent}.
|
||||
*
|
||||
* @param applicationEventPublisher
|
||||
* the {@link ApplicationEventPublisher}. Cannot be null.
|
||||
*/
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
Assert.notNull(applicationEventPublisher, "applicationEventPublisher cannot be null");
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
public void setAlwaysCreateSession(boolean alwaysCreateSession) {
|
||||
this.alwaysCreateSession = alwaysCreateSession;
|
||||
}
|
||||
|
||||
protected static final class NullEventPublisher implements
|
||||
ApplicationEventPublisher {
|
||||
public void publishEvent(ApplicationEvent event) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 java.lang.reflect.Method;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Uses {@link HttpServletRequest#changeSessionId()} to protect against session
|
||||
* fixation attacks. This is the default implementation for Servlet 3.1+.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 3.2
|
||||
*/
|
||||
public final class ChangeSessionIdAuthenticationStrategy extends AbstractSessionFixationProtectionStrategy {
|
||||
private final Method changeSessionIdMethod;
|
||||
|
||||
public ChangeSessionIdAuthenticationStrategy() {
|
||||
Method changeSessionIdMethod = ReflectionUtils.findMethod(HttpServletRequest.class, "changeSessionId");
|
||||
if(changeSessionIdMethod == null) {
|
||||
throw new IllegalStateException("HttpServletRequest.changeSessionId is undefined. Are you using a Servlet 3.1+ environment?");
|
||||
}
|
||||
this.changeSessionIdMethod = changeSessionIdMethod;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.security.web.authentication.session.AbstractSessionFixationProtectionStrategy#applySessionFixation(javax.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
HttpSession applySessionFixation(HttpServletRequest request) {
|
||||
ReflectionUtils.invokeMethod(changeSessionIdMethod, request);
|
||||
return request.getSession();
|
||||
}
|
||||
}
|
||||
+26
-115
@@ -22,19 +22,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
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}.
|
||||
* The default implementation of {@link SessionAuthenticationStrategy} when using < Servlet 3.1.
|
||||
* <p>
|
||||
* Creates a new session for the newly authenticated user if they already have a session (as a defence against
|
||||
* session-fixation protection attacks), and copies their session attributes across to the new session.
|
||||
@@ -59,19 +52,12 @@ import org.springframework.util.Assert;
|
||||
* @author Luke Taylor
|
||||
* @since 3.0
|
||||
*/
|
||||
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();
|
||||
|
||||
public class SessionFixationProtectionStrategy extends AbstractSessionFixationProtectionStrategy {
|
||||
/**
|
||||
* Indicates that the session attributes of an existing session
|
||||
* should be migrated to the new session. Defaults to <code>true</code>.
|
||||
*/
|
||||
private boolean migrateSessionAttributes = true;
|
||||
boolean migrateSessionAttributes = true;
|
||||
|
||||
/**
|
||||
* In the case where the attributes will not be migrated, this field allows a list of named attributes
|
||||
@@ -79,82 +65,6 @@ public class SessionFixationProtectionStrategy implements SessionAuthenticationS
|
||||
*/
|
||||
private List<String> retainedAttributes = null;
|
||||
|
||||
/**
|
||||
* If set to {@code true}, a session will always be created, even if one didn't exist at the start of the request.
|
||||
* Defaults to {@code false}.
|
||||
*/
|
||||
private boolean alwaysCreateSession;
|
||||
|
||||
/**
|
||||
* Called when a user is newly authenticated.
|
||||
* <p>
|
||||
* If a session already exists, and matches the session Id from the client, a new session will be created, and the
|
||||
* session attributes copied to it (if {@code migrateSessionAttributes} is set).
|
||||
* If the client's requested session Id is invalid, nothing will be done, since there is no need to change the
|
||||
* session Id if it doesn't match the current session.
|
||||
* <p>
|
||||
* If there is no session, no action is taken unless the {@code alwaysCreateSession} property is set, in which
|
||||
* case a session will be created if one doesn't already exist.
|
||||
*/
|
||||
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
|
||||
boolean hadSessionAlready = request.getSession(false) != null;
|
||||
|
||||
if (!hadSessionAlready && !alwaysCreateSession) {
|
||||
// Session fixation isn't a problem if there's no session
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new session if necessary
|
||||
HttpSession session = request.getSession();
|
||||
|
||||
if (hadSessionAlready && request.isRequestedSessionIdValid()) {
|
||||
// We need to migrate to a new session
|
||||
String originalSessionId = session.getId();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Invalidating session with Id '" + originalSessionId +"' " + (migrateSessionAttributes ?
|
||||
"and" : "without") + " migrating attributes.");
|
||||
}
|
||||
|
||||
Map<String, Object> attributesToMigrate = extractAttributes(session);
|
||||
|
||||
session.invalidate();
|
||||
session = request.getSession(true); // we now have a new session
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Started new session: " + session.getId());
|
||||
}
|
||||
|
||||
if (originalSessionId.equals(session.getId())) {
|
||||
logger.warn("Your servlet container did not change the session ID when a new session was created. You will" +
|
||||
" not be adequately protected against session-fixation attacks");
|
||||
}
|
||||
|
||||
transferAttributes(attributesToMigrate, session);
|
||||
|
||||
onSessionChange(originalSessionId, session, authentication);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to extract the existing attributes from the session, prior to invalidating it. If
|
||||
* {@code migrateAttributes} is set to {@code false}, only Spring Security attributes will be retained.
|
||||
@@ -169,11 +79,33 @@ public class SessionFixationProtectionStrategy implements SessionAuthenticationS
|
||||
return createMigratedAttributeMap(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
final HttpSession applySessionFixation(HttpServletRequest request) {
|
||||
HttpSession session = request.getSession();
|
||||
String originalSessionId = session.getId();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Invalidating session with Id '" + originalSessionId +"' " + (migrateSessionAttributes ?
|
||||
"and" : "without") + " migrating attributes.");
|
||||
}
|
||||
|
||||
Map<String, Object> attributesToMigrate = extractAttributes(session);
|
||||
|
||||
session.invalidate();
|
||||
session = request.getSession(true); // we now have a new session
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Started new session: " + session.getId());
|
||||
}
|
||||
|
||||
transferAttributes(attributesToMigrate, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param attributes the attributes which were extracted from the original session by {@code extractAttributes}
|
||||
* @param newSession the newly created session
|
||||
*/
|
||||
private void transferAttributes(Map<String, Object> attributes, HttpSession newSession) {
|
||||
void transferAttributes(Map<String, Object> attributes, HttpSession newSession) {
|
||||
if (attributes != null) {
|
||||
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
|
||||
newSession.setAttribute(entry.getKey(), entry.getValue());
|
||||
@@ -214,19 +146,6 @@ public class SessionFixationProtectionStrategy implements SessionAuthenticationS
|
||||
return attributesToMigrate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ApplicationEventPublisher} to use for submitting
|
||||
* {@link SessionFixationProtectionEvent}. The default is to not submit the
|
||||
* {@link SessionFixationProtectionEvent}.
|
||||
*
|
||||
* @param applicationEventPublisher
|
||||
* the {@link ApplicationEventPublisher}. Cannot be null.
|
||||
*/
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
Assert.notNull(applicationEventPublisher, "applicationEventPublisher cannot be null");
|
||||
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.
|
||||
@@ -250,12 +169,4 @@ public class SessionFixationProtectionStrategy implements SessionAuthenticationS
|
||||
Assert.notNull(retainedAttributes);
|
||||
this.retainedAttributes = retainedAttributes;
|
||||
}
|
||||
|
||||
public void setAlwaysCreateSession(boolean alwaysCreateSession) {
|
||||
this.alwaysCreateSession = alwaysCreateSession;
|
||||
}
|
||||
|
||||
private static final class NullEventPublisher implements ApplicationEventPublisher {
|
||||
public void publishEvent(ApplicationEvent event) { }
|
||||
}
|
||||
}
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.mockito.Matchers.*;
|
||||
import static org.powermock.api.mockito.PowerMockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({ReflectionUtils.class, Method.class})
|
||||
public class ChangeSessionIdAuthenticationStrategyTests {
|
||||
@Mock
|
||||
private Method method;
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void constructChangeIdMethodNotFound() {
|
||||
new ChangeSessionIdAuthenticationStrategy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applySessionFixation() throws Exception {
|
||||
spy(ReflectionUtils.class);
|
||||
Method method = mock(Method.class);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.getSession();
|
||||
when(ReflectionUtils.findMethod(HttpServletRequest.class, "changeSessionId")).thenReturn(method);
|
||||
|
||||
new ChangeSessionIdAuthenticationStrategy().applySessionFixation(request);
|
||||
|
||||
verifyStatic();
|
||||
ReflectionUtils.invokeMethod(same(method), eq(request));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user