SEC-1211: Create strategy for session handling on successful authentication. Added AuthenticatedSessionStrategy interface and default implementation which encapsulates the functionality that was previously in SessionFixationProtectionFilter and AbstractAuthentictationProcessingFilter. Updated the namespace to make use of these.

This commit is contained in:
Luke Taylor 2009-07-28 18:00:24 +00:00
parent 4a12b80470
commit db90122179
12 changed files with 372 additions and 194 deletions

View File

@ -40,18 +40,21 @@ public class FormLoginBeanDefinitionParser {
private final String defaultLoginProcessingUrl;
private final String filterClassName;
private final BeanReference requestCache;
private final BeanReference sessionStrategy;
private RootBeanDefinition filterBean;
private RootBeanDefinition entryPointBean;
private String loginPage;
FormLoginBeanDefinitionParser(String defaultLoginProcessingUrl, String filterClassName, BeanReference requestCache) {
FormLoginBeanDefinitionParser(String defaultLoginProcessingUrl, String filterClassName,
BeanReference requestCache, BeanReference sessionStrategy) {
this.defaultLoginProcessingUrl = defaultLoginProcessingUrl;
this.filterClassName = filterClassName;
this.requestCache = requestCache;
this.sessionStrategy = sessionStrategy;
}
public BeanDefinition parse(Element elt, ParserContext pc, RootBeanDefinition sfpf) {
public BeanDefinition parse(Element elt, ParserContext pc) {
String loginUrl = null;
String defaultTargetUrl = null;
String authenticationFailureUrl = null;
@ -84,12 +87,6 @@ public class FormLoginBeanDefinitionParser {
successHandlerRef, failureHandlerRef);
filterBean.setSource(source);
// Copy session migration values from the session fixation protection filter
if (sfpf != null) {
filterBean.getPropertyValues().addPropertyValue("migrateInvalidatedSessionAttributes", sfpf.getPropertyValues().getPropertyValue("migrateSessionAttributes").getValue());
filterBean.getPropertyValues().addPropertyValue("invalidateSessionOnSuccessfulAuthentication", Boolean.TRUE);
}
BeanDefinitionBuilder entryPointBuilder =
BeanDefinitionBuilder.rootBeanDefinition(LoginUrlAuthenticationEntryPoint.class);
entryPointBuilder.getRawBeanDefinition().setSource(source);
@ -122,6 +119,10 @@ public class FormLoginBeanDefinitionParser {
filterBuilder.addPropertyValue("authenticationSuccessHandler", successHandler.getBeanDefinition());
}
if (sessionStrategy != null) {
filterBuilder.addPropertyValue("authenticatedSessionStrategy", sessionStrategy);
}
if (StringUtils.hasText(failureHandlerRef)) {
filterBuilder.addPropertyReference("authenticationFailureHandler", failureHandlerRef);
} else {

View File

@ -66,6 +66,7 @@ import org.springframework.security.web.context.HttpSessionSecurityContextReposi
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.session.DefaultAuthenticatedSessionStrategy;
import org.springframework.security.web.session.SessionFixationProtectionFilter;
import org.springframework.security.web.util.AntUrlPathMatcher;
import org.springframework.security.web.util.RegexUrlPathMatcher;
@ -189,6 +190,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
BeanDefinition concurrentSessionFilter = createConcurrentSessionFilterAndRelatedBeansIfRequired(element, pc);
BeanDefinition scpf = createSecurityContextPersistenceFilter(element, pc);
BeanReference contextRepoRef = (BeanReference) scpf.getPropertyValues().getPropertyValue("securityContextRepository").getValue();
if (concurrentSessionFilter != null) {
sessionRegistryRef = (BeanReference)
@ -214,7 +216,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
BeanDefinition etf = createExceptionTranslationFilter(element, pc, requestCache);
RootBeanDefinition sfpf = createSessionFixationProtectionFilter(pc, element.getAttribute(ATT_SESSION_FIXATION_PROTECTION),
sessionRegistryRef);
sessionRegistryRef, contextRepoRef);
BeanReference sessionStrategyRef = null;
if (sfpf != null) {
sessionStrategyRef = (BeanReference) sfpf.getPropertyValues().getPropertyValue("authenticatedSessionStrategy").getValue();
}
BeanDefinition fsi = createFilterSecurityInterceptor(element, pc, matcher, convertPathsToLowerCase, authenticationManager);
if (channelRequestMap.size() > 0) {
@ -222,16 +229,11 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
cpf = createChannelProcessingFilter(pc, matcher, channelRequestMap, portMapperName);
}
// if (sfpf != null) {
// // Used by SessionRegistryinjectionPP
// pc.getRegistry().registerBeanDefinition(BeanIds.SESSION_FIXATION_PROTECTION_FILTER, sfpf);
// }
final FilterAndEntryPoint basic = createBasicFilter(element, pc, autoConfig, authenticationManager);
final FilterAndEntryPoint form = createFormLoginFilter(element, pc, autoConfig, allowSessionCreation,
sfpf, authenticationManager, requestCache);
sessionStrategyRef, authenticationManager, requestCache);
final FilterAndEntryPoint openID = createOpenIDLoginFilter(element, pc, autoConfig, allowSessionCreation,
sfpf, authenticationManager, requestCache);
sessionStrategyRef, authenticationManager, requestCache);
String rememberMeServicesId = null;
if (rememberMeFilter != null) {
@ -247,7 +249,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
pc.getRegistry().registerBeanDefinition(BeanIds.FORM_LOGIN_FILTER, form.filter);
pc.registerBeanComponent(new BeanComponentDefinition(form.filter, BeanIds.FORM_LOGIN_FILTER));
injectRememberMeServicesRef(form.filter, rememberMeServicesId);
injectSessionRegistryRef(form.filter, sessionRegistryRef);
}
if (openID.filter != null) {
@ -255,7 +256,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
pc.getRegistry().registerBeanDefinition(BeanIds.OPEN_ID_FILTER, openID.filter);
pc.registerBeanComponent(new BeanComponentDefinition(openID.filter, BeanIds.OPEN_ID_FILTER));
injectRememberMeServicesRef(openID.filter, rememberMeServicesId);
injectSessionRegistryRef(openID.filter, sessionRegistryRef);
}
String x509ProviderId = null;
@ -385,12 +385,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
}
}
private void injectSessionRegistryRef(RootBeanDefinition bean, BeanReference sessionRegistryRef){
if (sessionRegistryRef != null) {
bean.getPropertyValues().addPropertyValue("sessionRegistry", sessionRegistryRef);
}
}
private void checkFilterChainOrder(List<OrderDecorator> filters, ParserContext pc, Object source) {
logger.info("Checking sorted filter chain: " + filters);
@ -691,7 +685,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
contextRepo.addPropertyValue("disableUrlRewriting", Boolean.TRUE);
}
scpf.addPropertyValue("securityContextRepository", contextRepo.getBeanDefinition());
String id = pc.getReaderContext().registerWithGeneratedName(contextRepo.getBeanDefinition());
scpf.addPropertyReference("securityContextRepository", id);
}
return scpf.getBeanDefinition();
@ -914,7 +910,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
}
private RootBeanDefinition createSessionFixationProtectionFilter(ParserContext pc, String sessionFixationAttribute,
BeanReference sessionRegistryRef) {
BeanReference sessionRegistryRef, BeanReference contextRepoRef) {
if(!StringUtils.hasText(sessionFixationAttribute)) {
sessionFixationAttribute = OPT_SESSION_FIXATION_MIGRATE_SESSION;
}
@ -922,18 +918,25 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
if (!sessionFixationAttribute.equals(OPT_SESSION_FIXATION_NO_PROTECTION)) {
BeanDefinitionBuilder sessionFixationFilter =
BeanDefinitionBuilder.rootBeanDefinition(SessionFixationProtectionFilter.class);
sessionFixationFilter.addPropertyValue("migrateSessionAttributes",
sessionFixationFilter.addConstructorArgValue(contextRepoRef);
BeanDefinitionBuilder sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(DefaultAuthenticatedSessionStrategy.class);
sessionStrategy.addPropertyValue("migrateSessionAttributes",
Boolean.valueOf(sessionFixationAttribute.equals(OPT_SESSION_FIXATION_MIGRATE_SESSION)));
if (sessionRegistryRef != null) {
sessionFixationFilter.addPropertyValue("sessionRegistry", sessionRegistryRef);
sessionStrategy.addPropertyValue("sessionRegistry", sessionRegistryRef);
}
String id = pc.getReaderContext().registerWithGeneratedName(sessionStrategy.getBeanDefinition());
sessionFixationFilter.addPropertyReference("authenticatedSessionStrategy", id);
return (RootBeanDefinition) sessionFixationFilter.getBeanDefinition();
}
return null;
}
private FilterAndEntryPoint createFormLoginFilter(Element element, ParserContext pc, boolean autoConfig,
boolean allowSessionCreation, RootBeanDefinition sfpf, BeanReference authManager, BeanReference requestCache) {
boolean allowSessionCreation, BeanReference sessionStrategy, BeanReference authManager, BeanReference requestCache) {
RootBeanDefinition formLoginFilter = null;
RootBeanDefinition formLoginEntryPoint = null;
@ -941,9 +944,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
if (formLoginElt != null || autoConfig) {
FormLoginBeanDefinitionParser parser = new FormLoginBeanDefinitionParser("/j_spring_security_check",
AUTHENTICATION_PROCESSING_FILTER_CLASS, requestCache);
AUTHENTICATION_PROCESSING_FILTER_CLASS, requestCache, sessionStrategy);
parser.parse(formLoginElt, pc, sfpf);
parser.parse(formLoginElt, pc);
formLoginFilter = parser.getFilterBean();
formLoginEntryPoint = parser.getEntryPointBean();
}
@ -957,16 +960,16 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
}
private FilterAndEntryPoint createOpenIDLoginFilter(Element element, ParserContext pc, boolean autoConfig,
boolean allowSessionCreation, RootBeanDefinition sfpf, BeanReference authManager, BeanReference requestCache) {
boolean allowSessionCreation, BeanReference sessionStrategy, BeanReference authManager, BeanReference requestCache) {
Element openIDLoginElt = DomUtils.getChildElementByTagName(element, Elements.OPENID_LOGIN);
RootBeanDefinition openIDFilter = null;
RootBeanDefinition openIDEntryPoint = null;
if (openIDLoginElt != null) {
FormLoginBeanDefinitionParser parser = new FormLoginBeanDefinitionParser("/j_spring_openid_security_check",
OPEN_ID_AUTHENTICATION_PROCESSING_FILTER_CLASS, requestCache);
OPEN_ID_AUTHENTICATION_PROCESSING_FILTER_CLASS, requestCache, sessionStrategy);
parser.parse(openIDLoginElt, pc, sfpf);
parser.parse(openIDLoginElt, pc);
openIDFilter = parser.getFilterBean();
openIDEntryPoint = parser.getEntryPointBean();
}

View File

@ -647,10 +647,10 @@ public class HttpSecurityBeanDefinitionParserTests {
Object sessionRegistryFromConcurrencyFilter = FieldUtils.getFieldValue(
getFilter(ConcurrentSessionFilter.class),"sessionRegistry");
Object sessionRegistryFromFormLoginFilter = FieldUtils.getFieldValue(
getFilter(UsernamePasswordAuthenticationProcessingFilter.class),"sessionRegistry");
getFilter(UsernamePasswordAuthenticationProcessingFilter.class),"sessionStrategy.sessionRegistry");
Object sessionRegistryFromController = FieldUtils.getFieldValue(getConcurrentSessionController(),"sessionRegistry");
Object sessionRegistryFromFixationFilter = FieldUtils.getFieldValue(
getFilter(SessionFixationProtectionFilter.class),"sessionRegistry");
getFilter(SessionFixationProtectionFilter.class),"sessionStrategy.sessionRegistry");
assertSame(sessionRegistry, sessionRegistryFromConcurrencyFilter);
assertSame(sessionRegistry, sessionRegistryFromController);

View File

@ -31,14 +31,14 @@ import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.concurrent.SessionRegistry;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.SpringSecurityFilter;
import org.springframework.security.web.session.SessionUtils;
import org.springframework.security.web.session.AuthenticatedSessionStrategy;
import org.springframework.security.web.session.DefaultAuthenticatedSessionStrategy;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
@ -61,7 +61,9 @@ import org.springframework.util.Assert;
*
* If authentication is successful, the resulting {@link Authentication} object will be placed into the
* <code>SecurityContext</code> for the current thread, which is guaranteed to have already been created by an earlier
* filter. The configured {@link #setAuthenticationSuccessHandler(AuthenticationSuccessHandler) AuthenticationSuccessHandler} will
* filter.
* <p>
* The configured {@link #setAuthenticationSuccessHandler(AuthenticationSuccessHandler) AuthenticationSuccessHandler} will
* then be called to take the redirect to the appropriate destination after a successful login. The default behaviour
* is implemented in a {@link SavedRequestAwareAuthenticationSuccessHandler} which will make use of any
* <tt>SavedRequest</tt> set by the <tt>ExceptionTranslationFilter</tt> and redirect the user to the URL contained
@ -127,26 +129,10 @@ public abstract class AbstractAuthenticationProcessingFilter extends SpringSecur
private boolean continueChainBeforeSuccessfulAuthentication = false;
/**
* Tells if we on successful authentication should invalidate the
* current session. This is a common guard against session fixation attacks.
* Defaults to <code>false</code>.
*/
private boolean invalidateSessionOnSuccessfulAuthentication = false;
/**
* If {@link #invalidateSessionOnSuccessfulAuthentication} is true, this
* flag indicates that the session attributes of the session to be invalidated
* are to be migrated to the new session. Defaults to <code>true</code> since
* nothing will happen unless {@link #invalidateSessionOnSuccessfulAuthentication}
* is true.
*/
private boolean migrateInvalidatedSessionAttributes = true;
private AuthenticatedSessionStrategy sessionStrategy = new DefaultAuthenticatedSessionStrategy();
private boolean allowSessionCreation = true;
private SessionRegistry sessionRegistry;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
@ -281,7 +267,8 @@ public abstract class AbstractAuthenticationProcessingFilter extends SpringSecur
* Default behaviour for successful authentication.
* <ol>
* <li>Sets the successful <tt>Authentication</tt> object on the {@link SecurityContextHolder}</li>
* <li>Performs any configured session migration behaviour</li>
* <li>Invokes the configured {@link AuthenticatedSessionStrategy} to handle any session-related behaviour
* (such as creating a new session to protect against session-fixation attacks).</li>
* <li>Informs the configured <tt>RememberMeServices</tt> of the successful login</li>
* <li>Fires an {@link InteractiveAuthenticationSuccessEvent} via the configured
* <tt>ApplicationEventPublisher</tt></li>
@ -299,9 +286,7 @@ public abstract class AbstractAuthenticationProcessingFilter extends SpringSecur
SecurityContextHolder.getContext().setAuthentication(authResult);
if (invalidateSessionOnSuccessfulAuthentication) {
SessionUtils.startNewSessionIfRequired(request, migrateInvalidatedSessionAttributes, sessionRegistry);
}
sessionStrategy.onAuthenticationSuccess(authResult, request, response);
rememberMeServices.loginSuccess(request, response, authResult);
@ -332,14 +317,10 @@ public abstract class AbstractAuthenticationProcessingFilter extends SpringSecur
logger.debug("Delegating to authentication failure handler" + failureHandler);
}
try {
HttpSession session = request.getSession(false);
HttpSession session = request.getSession(false);
if (session != null || allowSessionCreation) {
request.getSession().setAttribute(SPRING_SECURITY_LAST_EXCEPTION_KEY, failed);
}
}
catch (Exception ignored) {
if (session != null || allowSessionCreation) {
request.getSession().setAttribute(SPRING_SECURITY_LAST_EXCEPTION_KEY, failed);
}
rememberMeServices.loginFail(request, response);
@ -394,14 +375,6 @@ public abstract class AbstractAuthenticationProcessingFilter extends SpringSecur
this.messages = new MessageSourceAccessor(messageSource);
}
public void setInvalidateSessionOnSuccessfulAuthentication(boolean invalidateSessionOnSuccessfulAuthentication) {
this.invalidateSessionOnSuccessfulAuthentication = invalidateSessionOnSuccessfulAuthentication;
}
public void setMigrateInvalidatedSessionAttributes(boolean migrateInvalidatedSessionAttributes) {
this.migrateInvalidatedSessionAttributes = migrateInvalidatedSessionAttributes;
}
public AuthenticationDetailsSource getAuthenticationDetailsSource() {
// Required due to SEC-310
return authenticationDetailsSource;
@ -416,11 +389,15 @@ public abstract class AbstractAuthenticationProcessingFilter extends SpringSecur
}
/**
* The session registry needs to be set if session fixation attack protection is in use (and concurrent
* session control is enabled).
* The session handling strategy which will be invoked when an authentication request is
* successfully processed. Used, for example, to handle changing of the session identifier to prevent session
* fixation attacks.
*
* @param sessionStrategy the implementation to use. If not set a {@link DefaultAuthenticatedSessionStrategy} is
* used.
*/
public void setSessionRegistry(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
public void setAuthenticatedSessionStrategy(AuthenticatedSessionStrategy sessionStrategy) {
this.sessionStrategy = sessionStrategy;
}
/**

View File

@ -111,6 +111,16 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
}
}
public boolean containsContext(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
return session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) != null;
}
/**
*
* @param httpSession the session obtained from the request.

View File

@ -50,4 +50,13 @@ public interface SecurityContextRepository {
* @param response
*/
void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);
/**
* Allows the repository to be queried as to whether it contains a security context for the
* current request.
*
* @param request the current request
* @return true if a context is found for the request, false otherwise
*/
boolean containsContext(HttpServletRequest request);
}

View File

@ -0,0 +1,25 @@
package org.springframework.security.web.session;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
/**
* Allows pluggable support for Http session-related behaviour when an authentication occurs.
* <p>
* Typical use would be to make sure a session exists or to change the session Id to guard against session-fixation
* attacks.
*
* @author Luke Taylor
* @version $Id$
* @since
*/
public interface AuthenticatedSessionStrategy {
/**
* Performs Http session-related functionality when a new authentication occurs.
*/
void onAuthenticationSuccess(Authentication authentication, HttpServletRequest request, HttpServletResponse response);
}

View File

@ -0,0 +1,155 @@
package org.springframework.security.web.session;
import java.util.Arrays;
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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.concurrent.SessionRegistry;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.savedrequest.SavedRequest;
/**
* The default implementation of {@link AuthenticatedSessionStrategy}.
* <p>
* Creates a new session for the newly authenticated user if they already have a session, and copies their
* session attributes across to the new session (can be disabled by setting <tt>migrateSessionAttributes</tt> to
* <tt>false</tt>).
* <p>
* If concurrent session control is in use, then a <tt>SessionRegistry</tt> must be injected.
*
* @author Luke Taylor
* @version $Id$
* @since 3.0
*/
public class DefaultAuthenticatedSessionStrategy implements AuthenticatedSessionStrategy{
protected final Log logger = LogFactory.getLog(this.getClass());
private SessionRegistry sessionRegistry;
/**
* 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;
/**
* In the case where the attributes will not be migrated, this field allows a list of named attributes
* which should <em>not</em> be discarded.
*/
private List<String> retainedAttributes = Arrays.asList(SavedRequest.SPRING_SECURITY_SAVED_REQUEST_KEY);
/**
* If set to <tt>true</tt>, a session will always be created, even if one didn't exist at the start of the request.
* Defaults to <tt>false</tt>.
*/
private boolean alwaysCreateSession;
/**
* Called when a user is newly authenticated.
* <p>
* If a session already exists, a new session will be created, the session attributes copied to it (if
* <tt>migrateSessionAttributes</tt> is set) and the sessionRegistry updated with the new session information.
* <p>
* If there is no session, no action is taken unless the <tt>alwaysCreateSession</tt> property is set, in which
* case a session will be created if one doesn't already exist.
*/
public void onAuthenticationSuccess(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
if (request.getSession(false) == null) {
// Session fixation isn't a problem if there's no session
if (alwaysCreateSession) {
request.getSession();
}
return;
}
// Create new session
HttpSession session = request.getSession();
String originalSessionId = session.getId();
if (logger.isDebugEnabled()) {
logger.debug("Invalidating session with Id '" + originalSessionId +"' " + (migrateSessionAttributes ?
"and" : "without") + " migrating attributes.");
}
HashMap<String, Object> attributesToMigrate = createMigratedAttributeMap(session);
session.invalidate();
session = request.getSession(true); // we now have a new session
if (logger.isDebugEnabled()) {
logger.debug("Started new session: " + session.getId());
}
// Copy attributes to new session
if (attributesToMigrate != null) {
for (Map.Entry<String, Object> entry : attributesToMigrate.entrySet()) {
session.setAttribute(entry.getKey(), entry.getValue());
}
}
// Update the session registry
if (sessionRegistry != null) {
sessionRegistry.removeSessionInformation(originalSessionId);
sessionRegistry.registerNewSession(session.getId(),
SecurityContextHolder.getContext().getAuthentication().getPrincipal());
}
}
@SuppressWarnings("unchecked")
private HashMap<String, Object> createMigratedAttributeMap(HttpSession session) {
HashMap<String, Object> attributesToMigrate = null;
if (migrateSessionAttributes) {
attributesToMigrate = new HashMap<String, Object>();
Enumeration enumer = session.getAttributeNames();
while (enumer.hasMoreElements()) {
String key = (String) enumer.nextElement();
attributesToMigrate.put(key, session.getAttribute(key));
}
} else {
// Only retain the attributes which have been specified in the retainAttributes list
if (!retainedAttributes.isEmpty()) {
attributesToMigrate = new HashMap<String, Object>();
for (String name : retainedAttributes) {
Object value = session.getAttribute(name);
if (value != null) {
attributesToMigrate.put(name, value);
}
}
}
}
return attributesToMigrate;
}
public void setMigrateSessionAttributes(boolean migrateSessionAttributes) {
this.migrateSessionAttributes = migrateSessionAttributes;
}
/**
* Sets the session registry which should be updated when the authenticated session is changed.
* This must be set if you are using concurrent session control.
*
* @param sessionRegistry
*/
public void setSessionRegistry(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
public void setRetainedAttributes(List<String> retainedAttributes) {
this.retainedAttributes = retainedAttributes;
}
}

View File

@ -6,26 +6,25 @@ import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.concurrent.SessionRegistry;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.SpringSecurityFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.util.Assert;
/**
* Detects that a user has been authenticated since the start of the request and starts a new session.
* Detects that a user has been authenticated since the start of the request and, if they have, calls the
* configured {@link AuthenticatedSessionStrategy} to perform any session-related activity (such as
* activating session-fixation protection mechanisms).
* <p>
* This is essentially a generalization of the functionality that was implemented for SEC-399.
* Additionally, it will update the configured SessionRegistry if one is in use, thus preventing problems when used
* with Spring Security's concurrent session control.
*
* @author Martin Algesten
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public class SessionFixationProtectionFilter extends SpringSecurityFilter {
@ -35,59 +34,46 @@ public class SessionFixationProtectionFilter extends SpringSecurityFilter {
//~ Instance fields ================================================================================================
private SessionRegistry sessionRegistry;
private final SecurityContextRepository securityContextRepository;
/**
* Indicates that the session attributes of the session to be invalidated
* should be migrated to the new session. Defaults to <code>true</code>.
*/
private boolean migrateSessionAttributes = true;
private AuthenticatedSessionStrategy sessionStrategy = new DefaultAuthenticatedSessionStrategy();
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
public SessionFixationProtectionFilter(SecurityContextRepository securityContextRepository) {
this.securityContextRepository = securityContextRepository;
}
protected void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Session fixation isn't a problem if there's no session
if(request.getSession(false) == null || request.getAttribute(FILTER_APPLIED) != null) {
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
HttpSession session = request.getSession();
SecurityContext sessionSecurityContext =
(SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
if (!securityContextRepository.containsContext(request)) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (sessionSecurityContext == null && isAuthenticated()) {
// The user has been authenticated during the current request, so do the session migration
startNewSessionIfRequired(request, response);
if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)) {
// The user has been authenticated during the current request, so call the session strategy
sessionStrategy.onAuthenticationSuccess(authentication, request, response);
}
}
chain.doFilter(request, response);
}
private boolean isAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && !authenticationTrustResolver.isAnonymous(authentication);
}
public void setMigrateSessionAttributes(boolean migrateSessionAttributes) {
this.migrateSessionAttributes = migrateSessionAttributes;
}
public void setSessionRegistry(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
/**
* Called when the a user wasn't authenticated at the start of the request but has been during it
* <p>
* A new session will be created, the session attributes copied to it (if
* <tt>migrateSessionAttributes</tt> is set) and the sessionRegistry updated with the new session information.
* Sets the strategy object which handles the session management behaviour when a
* user has been authenticated during the current request.
*
* @param sessionStrategy the strategy object. If not set, a {@link DefaultAuthenticatedSessionStrategy} is used.
*/
protected void startNewSessionIfRequired(HttpServletRequest request, HttpServletResponse response) {
SessionUtils.startNewSessionIfRequired(request, migrateSessionAttributes, sessionRegistry);
public void setAuthenticatedSessionStrategy(AuthenticatedSessionStrategy sessionStrategy) {
Assert.notNull(sessionStrategy, "authenticatedSessionStratedy must not be null");
this.sessionStrategy = sessionStrategy;
}
}

View File

@ -50,6 +50,7 @@ import org.springframework.security.web.authentication.SavedRequestAwareAuthenti
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.security.web.session.AuthenticatedSessionStrategy;
/**
@ -239,6 +240,7 @@ public class AbstractProcessingFilterTests extends TestCase {
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
filter.setFilterProcessesUrl("/j_mock_post");
filter.setAuthenticatedSessionStrategy(mock(AuthenticatedSessionStrategy.class));
filter.setAuthenticationSuccessHandler(successHandler);
filter.setAuthenticationFailureHandler(failureHandler);
filter.setAuthenticationManager(mock(AuthenticationManager.class));
@ -390,48 +392,6 @@ public class AbstractProcessingFilterTests extends TestCase {
assertNotNull(SecurityContextHolder.getContext().getAuthentication());
}
public void testNewSessionIsCreatedIfInvalidateSessionOnSuccessfulAuthenticationIsSet() throws Exception {
MockHttpServletRequest request = createMockRequest();
HttpSession oldSession = request.getSession();
oldSession.setAttribute("test","test");
MockFilterConfig config = new MockFilterConfig(null, null);
MockFilterChain chain = new MockFilterChain(true);
MockHttpServletResponse response = new MockHttpServletResponse();
// Setup our test object, to grant access
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
filter.setInvalidateSessionOnSuccessfulAuthentication(true);
successHandler.setDefaultTargetUrl("http://monkeymachine.co.uk/");
filter.setAuthenticationSuccessHandler(successHandler);
executeFilterInContainerSimulator(config, filter, request, response, chain);
HttpSession newSession = request.getSession();
assertFalse(newSession.getId().equals(oldSession.getId()));
assertEquals("test", newSession.getAttribute("test"));
}
public void testAttributesAreNotMigratedToNewlyCreatedSessionIfMigrateAttributesIsFalse() throws Exception {
MockHttpServletRequest request = createMockRequest();
HttpSession oldSession = request.getSession();
MockFilterConfig config = new MockFilterConfig(null, null);
MockFilterChain chain = new MockFilterChain(true);
MockHttpServletResponse response = new MockHttpServletResponse();
MockAbstractProcessingFilter filter = new MockAbstractProcessingFilter(true);
filter.setInvalidateSessionOnSuccessfulAuthentication(true);
filter.setMigrateInvalidatedSessionAttributes(false);
successHandler.setDefaultTargetUrl("http://monkeymachine.co.uk/");
filter.setAuthenticationSuccessHandler(successHandler);
executeFilterInContainerSimulator(config, filter, request, response, chain);
HttpSession newSession = request.getSession();
assertFalse(newSession.getId().equals(oldSession.getId()));
assertNull(newSession.getAttribute("test"));
}
/**
* SEC-571
*/

View File

@ -0,0 +1,41 @@
package org.springframework.security.web.session;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.core.Authentication;
/**
*
* @author Luke Taylor
* @version $Id$
*/
public class DefaultAuthenticatedSessionStrategyTests {
@Test
public void newSessionShouldNotBeCreatedIfNoSessionExistsAndAlwaysCreateIsFalse() throws Exception {
DefaultAuthenticatedSessionStrategy strategy = new DefaultAuthenticatedSessionStrategy();
HttpServletRequest request = new MockHttpServletRequest();
strategy.onAuthenticationSuccess(mock(Authentication.class), request, new MockHttpServletResponse());
assertNull(request.getSession(false));
}
@Test
public void newSessionIsCreatedIfSessionAlreadyExists() throws Exception {
DefaultAuthenticatedSessionStrategy strategy = new DefaultAuthenticatedSessionStrategy();
HttpServletRequest request = new MockHttpServletRequest();
String sessionId = request.getSession().getId();
strategy.onAuthenticationSuccess(mock(Authentication.class), request, new MockHttpServletResponse());
assertFalse(sessionId.equals(request.getSession().getId()));
}
}

View File

@ -1,8 +1,11 @@
package org.springframework.security.web.session;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.After;
import org.junit.Test;
@ -10,9 +13,9 @@ 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.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.session.SessionFixationProtectionFilter;
import org.springframework.security.web.context.SecurityContextRepository;
/**
*
@ -26,32 +29,10 @@ public class SessionFixationProtectionFilterTests {
SecurityContextHolder.clearContext();
}
@Test
public void newSessionShouldNotBeCreatedIfNoSessionExists() throws Exception {
SessionFixationProtectionFilter filter = new SessionFixationProtectionFilter();
HttpServletRequest request = new MockHttpServletRequest();
authenticateUser();
filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
assertNull(request.getSession(false));
}
@Test
public void newSessionBeCreatedIfAuthenticatedOccurredDuringRequest() throws Exception {
SessionFixationProtectionFilter filter = new SessionFixationProtectionFilter();
HttpServletRequest request = new MockHttpServletRequest();
String sessionId = request.getSession().getId();
authenticateUser();
filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
assertFalse(sessionId.equals(request.getSession().getId()));
}
@Test
public void newSessionShouldNotBeCreatedIfSessionExistsAndUserIsNotAuthenticated() throws Exception {
SessionFixationProtectionFilter filter = new SessionFixationProtectionFilter();
SecurityContextRepository repo = mock(SecurityContextRepository.class);
SessionFixationProtectionFilter filter = new SessionFixationProtectionFilter(repo);
HttpServletRequest request = new MockHttpServletRequest();
String sessionId = request.getSession().getId();
@ -61,17 +42,47 @@ public class SessionFixationProtectionFilterTests {
}
@Test
public void newSessionShouldNotBeCreatedIfUserIsAlreadyAuthenticated() throws Exception {
SessionFixationProtectionFilter filter = new SessionFixationProtectionFilter();
public void strategyIsNotInvokedIfSecurityContextAlreadyExistsForRequest() throws Exception {
SecurityContextRepository repo = mock(SecurityContextRepository.class);
AuthenticatedSessionStrategy strategy = mock(AuthenticatedSessionStrategy.class);
// mock that repo contains a security context
when(repo.containsContext(any(HttpServletRequest.class))).thenReturn(true);
SessionFixationProtectionFilter filter = new SessionFixationProtectionFilter(repo);
filter.setAuthenticatedSessionStrategy(strategy);
HttpServletRequest request = new MockHttpServletRequest();
String sessionId = request.getSession().getId();
authenticateUser();
request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
assertEquals(sessionId, request.getSession().getId());
verifyZeroInteractions(strategy);
}
@Test
public void strategyIsNotInvokedIfAuthenticationIsNull() throws Exception {
SecurityContextRepository repo = mock(SecurityContextRepository.class);
AuthenticatedSessionStrategy strategy = mock(AuthenticatedSessionStrategy.class);
SessionFixationProtectionFilter filter = new SessionFixationProtectionFilter(repo);
filter.setAuthenticatedSessionStrategy(strategy);
HttpServletRequest request = new MockHttpServletRequest();
filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
verifyZeroInteractions(strategy);
}
@Test
public void strategyIsInvokedIfUserIsNewlyAuthenticated() throws Exception {
SecurityContextRepository repo = mock(SecurityContextRepository.class);
// repo will return false to containsContext()
AuthenticatedSessionStrategy strategy = mock(AuthenticatedSessionStrategy.class);
SessionFixationProtectionFilter filter = new SessionFixationProtectionFilter(repo);
filter.setAuthenticatedSessionStrategy(strategy);
HttpServletRequest request = new MockHttpServletRequest();
authenticateUser();
filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
verify(strategy).onAuthenticationSuccess(any(Authentication.class), any(HttpServletRequest.class), any(HttpServletResponse.class));
}
private void authenticateUser() {