SEC-2135: Support HttpServletRequest#changeSessionId()

This commit is contained in:
Rob Winch 2013-08-10 18:41:57 -05:00
parent 75fb971d23
commit 797df51264
10 changed files with 730 additions and 125 deletions

View File

@ -27,6 +27,7 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
@ -77,7 +78,7 @@ import org.springframework.util.Assert;
* @see ConcurrentSessionFilter
*/
public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = new SessionFixationProtectionStrategy();
private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = createDefaultSessionFixationProtectionStrategy();
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
private SessionRegistry sessionRegistry = new SessionRegistryImpl();
private Integer maximumSessions;
@ -186,6 +187,14 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
return new ConcurrencyControlConfigurer();
}
/**
* Invokes {@link #postProcess(Object)} and sets the {@link SessionAuthenticationStrategy} for session fixation.
* @param sessionFixationAuthenticationStrategy
*/
private void setSessionFixationAuthenticationStrategy(SessionAuthenticationStrategy sessionFixationAuthenticationStrategy) {
this.sessionFixationAuthenticationStrategy = postProcess(sessionFixationAuthenticationStrategy);
}
/**
* Allows configuring SessionFixation protection
*
@ -202,7 +211,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
public SessionManagementConfigurer<H> newSession() {
SessionFixationProtectionStrategy sessionFixationProtectionStrategy = new SessionFixationProtectionStrategy();
sessionFixationProtectionStrategy.setMigrateSessionAttributes(false);
SessionManagementConfigurer.this.sessionFixationAuthenticationStrategy = sessionFixationProtectionStrategy;
setSessionFixationAuthenticationStrategy(sessionFixationProtectionStrategy);
return SessionManagementConfigurer.this;
}
@ -214,7 +223,22 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
* @return the {@link SessionManagementConfigurer} for further customizations
*/
public SessionManagementConfigurer<H> migrateSession() {
SessionManagementConfigurer.this.sessionFixationAuthenticationStrategy = new SessionFixationProtectionStrategy();
setSessionFixationAuthenticationStrategy(new SessionFixationProtectionStrategy());
return SessionManagementConfigurer.this;
}
/**
* Specifies that no session fixation protection should be enabled. This
* may be useful when utilizing other mechanisms for protecting against
* session fixation. For example, if application container session
* fixation protection is already in use. Otherwise, this option is not
* recommended.
*
* @return the {@link SessionManagementConfigurer} for further
* customizations
*/
public SessionManagementConfigurer<H> changeSessionId() {
setSessionFixationAuthenticationStrategy(new ChangeSessionIdAuthenticationStrategy());
return SessionManagementConfigurer.this;
}
@ -229,7 +253,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
* customizations
*/
public SessionManagementConfigurer<H> none() {
SessionManagementConfigurer.this.sessionFixationAuthenticationStrategy = new NullAuthenticatedSessionStrategy();
setSessionFixationAuthenticationStrategy(new NullAuthenticatedSessionStrategy());
return SessionManagementConfigurer.this;
}
}
@ -400,4 +424,16 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
private boolean isConcurrentSessionControlEnabled() {
return maximumSessions != null;
}
/**
* Creates the default {@link SessionAuthenticationStrategy} for session fixation
* @return the default {@link SessionAuthenticationStrategy} for session fixation
*/
private static SessionAuthenticationStrategy createDefaultSessionFixationProtectionStrategy() {
try {
return new ChangeSessionIdAuthenticationStrategy();
} catch(IllegalStateException e) {
return new SessionFixationProtectionStrategy();
}
}
}

View File

@ -515,8 +515,8 @@ session-management =
element session-management {session-management.attlist, concurrency-control?}
session-management.attlist &=
## Indicates whether an existing session should be invalidated when a user authenticates and a new session started. If set to "none" no change will be made. "newSession" will create a new empty session. "migrateSession" will create a new session and copy the session attributes to the new session. Defaults to "migrateSession".
attribute session-fixation-protection {"none" | "newSession" | "migrateSession" }?
## Indicates how session fixation protection will be applied when a user authenticates. If set to "none", no protection will be applied. "newSession" will create a new empty session, with only Spring Security-related attributes migrated. "migrateSession" will create a new session and copy all session attributes to the new session. In Servlet 3.1 (Java EE 7) and newer containers, specifying "changeSessionId" will keep the existing session and use the container-supplied session fixation protection (HttpServletRequest#changeSessionId()). Defaults to "changeSessionId" in Servlet 3.1 and newer containers, "migrateSession" in older containers. Throws an exception if "changeSessionId" is used in older containers.
attribute session-fixation-protection {"none" | "newSession" | "migrateSession" | "changeSessionId" }?
session-management.attlist &=
## The URL to which a user will be redirected if they submit an invalid session indentifier. Typically used to detect session timeouts.
attribute invalid-session-url {xsd:token}?

View File

@ -1664,10 +1664,15 @@
<xs:attributeGroup name="session-management.attlist">
<xs:attribute name="session-fixation-protection">
<xs:annotation>
<xs:documentation>Indicates whether an existing session should be invalidated when a user authenticates and
a new session started. If set to "none" no change will be made. "newSession" will create a
new empty session. "migrateSession" will create a new session and copy the session
attributes to the new session. Defaults to "migrateSession".
<xs:documentation>Indicates how session fixation protection will be applied when a user authenticates. If
set to "none", no protection will be applied. "newSession" will create a new empty
session, with only Spring Security-related attributes migrated. "migrateSession" will
create a new session and copy all session attributes to the new session. In Servlet 3.1
(Java EE 7) and newer containers, specifying "changeSessionId" will keep the existing
session and use the container-supplied session fixation protection
(HttpServletRequest#changeSessionId()). Defaults to "changeSessionId" in Servlet 3.1 and
newer containers, "migrateSession" in older containers. Throws an exception if
"changeSessionId" is used in older containers.
</xs:documentation>
</xs:annotation>
<xs:simpleType>
@ -1675,6 +1680,7 @@
<xs:enumeration value="none"/>
<xs:enumeration value="newSession"/>
<xs:enumeration value="migrateSession"/>
<xs:enumeration value="changeSessionId"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>

View File

@ -0,0 +1,101 @@
/*
* Copyright 2002-2012 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.config.http;
import java.security.Principal
import javax.servlet.Filter
import org.springframework.beans.BeansException
import org.springframework.beans.factory.BeanCreationException
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.SecurityConfig
import org.springframework.security.authentication.AnonymousAuthenticationProvider;
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.BeanIds
import org.springframework.security.config.MockUserServiceBeanPostProcessor
import org.springframework.security.config.PostProcessedMockUserDetailsService
import org.springframework.security.config.util.InMemoryXmlApplicationContext
import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.openid.OpenIDAuthenticationFilter
import org.springframework.security.util.FieldUtils
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.PortMapperImpl
import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.access.channel.ChannelProcessingFilter
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.authentication.logout.LogoutFilter
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter
import org.springframework.security.web.savedrequest.HttpSessionRequestCache
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
import org.springframework.security.web.session.SessionManagementFilter
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler
import org.springframework.security.web.firewall.DefaultHttpFirewall
import org.springframework.security.BeanNameCollectingPostProcessor
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.access.vote.RoleVoter
import org.springframework.security.web.access.expression.WebExpressionVoter
import org.springframework.security.access.vote.AffirmativeBased
import org.springframework.security.access.PermissionEvaluator
import org.springframework.security.core.Authentication
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler
import org.springframework.security.web.util.AntPathRequestMatcher
import org.springframework.security.authentication.AuthenticationManager
/**
*
* @author Rob Winch
*/
class InterceptUrlConfigTests extends AbstractHttpConfigTests {
def "SEC-2256: intercept-url method is not given priority"() {
when:
httpAutoConfig {
'intercept-url'(pattern: '/anyurl', access: "ROLE_USER")
'intercept-url'(pattern: '/anyurl', 'method':'GET',access: 'ROLE_ADMIN')
}
createAppContext()
def fids = getFilter(FilterSecurityInterceptor).securityMetadataSource
def attrs = fids.getAttributes(createFilterinvocation("/anyurl", "GET"))
def attrsPost = fids.getAttributes(createFilterinvocation("/anyurl", "POST"))
then:
attrs.size() == 1
attrs.contains(new SecurityConfig("ROLE_USER"))
attrsPost.size() == 1
attrsPost.contains(new SecurityConfig("ROLE_USER"))
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.config.annotation.web.configurers;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import org.junit.After;
import org.junit.Before;
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.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.Authentication;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.util.ReflectionUtils;
/**
*
* @author Rob Winch
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ReflectionUtils.class, Method.class})
public class SessionManagementConfigurerServlet31Tests {
@Mock
Method method;
MockHttpServletRequest request;
MockHttpServletResponse response;
MockFilterChain chain;
ConfigurableApplicationContext context;
Filter springSecurityFilterChain;
@Before
public void setup() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
chain = new MockFilterChain();
}
@After
public void teardown() {
if(context != null) {
context.close();
}
}
@Test
public void changeSessionIdDefaultsInServlet31Plus() throws Exception {
spy(ReflectionUtils.class);
Method method = mock(Method.class);
MockHttpServletRequest request = new MockHttpServletRequest();
request.getSession();
request.setServletPath("/login");
request.setMethod("POST");
request.setParameter("username", "user");
request.setParameter("password", "password");
when(ReflectionUtils.findMethod(HttpServletRequest.class, "changeSessionId")).thenReturn(method);
loadConfig(SessionManagementDefaultSessionFixationServlet31Config.class);
springSecurityFilterChain.doFilter(request,response,chain);
verifyStatic();
ReflectionUtils.invokeMethod(same(method), any(HttpServletRequest.class));
}
@EnableWebSecurity
@Configuration
static class SessionManagementDefaultSessionFixationServlet31Config extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.sessionManagement();
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
private void loadConfig(Class<?>...classes) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(classes);
context.refresh();
this.context = context;
this.springSecurityFilterChain = this.context.getBean("springSecurityFilterChain",Filter.class);
}
private void login(Authentication auth) {
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response);
repo.loadContext(requestResponseHolder);
SecurityContextImpl securityContextImpl = new SecurityContextImpl();
securityContextImpl.setAuthentication(auth);
repo.saveContext(securityContextImpl, requestResponseHolder.getRequest(), requestResponseHolder.getResponse());
}
}

View File

@ -0,0 +1,158 @@
/*
* 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.config.http;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.same;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import org.junit.After;
import org.junit.Before;
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.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.util.InMemoryXmlApplicationContext;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.util.ReflectionUtils;
/**
* @author Rob Winch
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({ReflectionUtils.class, Method.class})
public class SessionManagementConfigServlet31Tests {
private static final String XML_AUTHENTICATION_MANAGER =
"<authentication-manager>"+
" <authentication-provider>"+
" <user-service>"+
" <user name='user' password='password' authorities='ROLE_USER' />" +
" </user-service>"+
" </authentication-provider>"+
"</authentication-manager>";
@Mock
Method method;
MockHttpServletRequest request;
MockHttpServletResponse response;
MockFilterChain chain;
ConfigurableApplicationContext context;
Filter springSecurityFilterChain;
@Before
public void setup() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
chain = new MockFilterChain();
}
@After
public void teardown() {
if(context != null) {
context.close();
}
}
@Test
public void changeSessionIdDefaultsInServlet31Plus() throws Exception {
spy(ReflectionUtils.class);
Method method = mock(Method.class);
MockHttpServletRequest request = new MockHttpServletRequest();
request.getSession();
request.setRequestURI("/j_spring_security_check");
request.setMethod("POST");
request.setParameter("j_username", "user");
request.setParameter("j_password", "password");
when(ReflectionUtils.findMethod(HttpServletRequest.class, "changeSessionId")).thenReturn(method);
loadContext("<http>\n" +
" <form-login/>\n" +
" <session-management/>\n" +
" </http>" +
XML_AUTHENTICATION_MANAGER);
springSecurityFilterChain.doFilter(request,response,chain);
verifyStatic();
ReflectionUtils.invokeMethod(same(method), any(HttpServletRequest.class));
}
@Test
public void changeSessionId() throws Exception {
spy(ReflectionUtils.class);
Method method = mock(Method.class);
MockHttpServletRequest request = new MockHttpServletRequest();
request.getSession();
request.setRequestURI("/j_spring_security_check");
request.setMethod("POST");
request.setParameter("j_username", "user");
request.setParameter("j_password", "password");
when(ReflectionUtils.findMethod(HttpServletRequest.class, "changeSessionId")).thenReturn(method);
loadContext("<http>\n" +
" <form-login/>\n" +
" <session-management session-fixation-protection='changeSessionId'/>\n" +
" </http>" +
XML_AUTHENTICATION_MANAGER);
springSecurityFilterChain.doFilter(request,response,chain);
verifyStatic();
ReflectionUtils.invokeMethod(same(method), any(HttpServletRequest.class));
}
private void loadContext(String context) {
this.context = new InMemoryXmlApplicationContext(context);
this.springSecurityFilterChain = this.context.getBean("springSecurityFilterChain",Filter.class);
}
private void login(Authentication auth) {
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response);
repo.loadContext(requestResponseHolder);
SecurityContextImpl securityContextImpl = new SecurityContextImpl();
securityContextImpl.setAuthentication(auth);
repo.saveContext(securityContextImpl, requestResponseHolder.getRequest(), requestResponseHolder.getResponse());
}
}

View File

@ -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) {
}
}
}

View File

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

View File

@ -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) { }
}
}

View File

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