From e2f9be901541ff7c1f219d878fbd82d6289fd81a Mon Sep 17 00:00:00 2001
From: Luke Taylor HttpSession.
- *
- *
- * The HttpSession will be queried to retrieve the
- * SecurityContext that should be stored against the
- * SecurityContextHolder for the duration of the web request. At
- * the end of the web request, any updates made to the
- * SecurityContextHolder will be persisted back to the
- * HttpSession by this filter.
- *
SecurityContext cannot be obtained from the
- * HttpSession for whatever reason, a fresh
- * SecurityContext will be created and used instead. The created
- * object will be of the instance defined by the {@link #setContextClass(Class)}
- * method (which defaults to {@link org.springframework.security.core.context.SecurityContextImpl}.
- *
- *
- * No HttpSession will be created by this filter if one does not
- * already exist. If at the end of the web request the HttpSession
- * does not exist, a HttpSession will only be created if
- * the current contents of the SecurityContextHolder are not
- * {@link java.lang.Object#equals(java.lang.Object)} to a new
- * instance of {@link #setContextClass(Class)}. This avoids needless
- * HttpSession creation, but automates the storage of changes
- * made to the SecurityContextHolder. There is one exception to
- * this rule, that is if the {@link #forceEagerSessionCreation} property is
- * true, in which case sessions will always be created
- * irrespective of normal session-minimisation logic (the default is
- * false, as this is resource intensive and not recommended).
- *
- *
- * This filter will only execute once per request, to resolve servlet container
- * (specifically Weblogic) incompatibilities.
- *
- *
- * If for whatever reason no HttpSession should ever be
- * created (eg this filter is only being used with Basic authentication or
- * similar clients that will never present the same jsessionid
- * etc), the {@link #setAllowSessionCreation(boolean)} should be set to
- * false. Only do this if you really need to conserve server
- * memory and ensure all classes using the SecurityContextHolder
- * are designed to have no persistence of the SecurityContext
- * between web requests. Please note that if {@link #forceEagerSessionCreation}
- * is true, the allowSessionCreation must also be
- * true (setting it to false will cause a startup
- * time error).
- *
- *
- * This filter MUST be executed BEFORE any authentication processing mechanisms.
- * Authentication processing mechanisms (eg BASIC, CAS processing filters etc)
- * expect the SecurityContextHolder to contain a valid
- * SecurityContext by the time they execute.
- *
- *
- * @author Ben Alex
- * @author Patrick Burleson
- * @author Luke Taylor
- * @author Martin Algesten
- *
- * @deprecated Use SecurityContextPersistenceFilter instead.
- *
- */
-public class HttpSessionContextIntegrationFilter extends SecurityContextPersistenceFilter implements InitializingBean {
- //~ Static fields/initializers =====================================================================================
- public static final String SPRING_SECURITY_CONTEXT_KEY = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
-
- //~ Instance fields ================================================================================================
-
- private Class extends SecurityContext> contextClass = SecurityContextImpl.class;
-
- /**
- * Indicates if this filter can create a HttpSession if
- * needed (sessions are always created sparingly, but setting this value to
- * false will prohibit sessions from ever being created).
- * Defaults to true. Do not set to false if
- * you are have set {@link #forceEagerSessionCreation} to true,
- * as the properties would be in conflict.
- */
- private boolean allowSessionCreation = true;
-
- /**
- * Indicates if this filter is required to create a HttpSession
- * for every request before proceeding through the filter chain, even if the
- * HttpSession would not ordinarily have been created. By
- * default this is false, which is entirely appropriate for
- * most circumstances as you do not want a HttpSession
- * created unless the filter actually needs one. It is envisaged the main
- * situation in which this property would be set to true is
- * if using other filters that depend on a HttpSession
- * already existing, such as those which need to obtain a session ID. This
- * is only required in specialised cases, so leave it set to
- * false unless you have an actual requirement and are
- * conscious of the session creation overhead.
- */
- private boolean forceEagerSessionCreation = false;
-
- /**
- * Indicates whether the SecurityContext will be cloned from
- * the HttpSession. The default is to simply reference (ie
- * the default is false). The default may cause issues if
- * concurrent threads need to have a different security identity from other
- * threads being concurrently processed that share the same
- * HttpSession. In most normal environments this does not
- * represent an issue, as changes to the security identity in one thread is
- * allowed to affect the security identitiy in other threads associated with
- * the same HttpSession. For unusual cases where this is not
- * permitted, change this value to true and ensure the
- * {@link #contextClass} is set to a SecurityContext that
- * implements {@link Cloneable} and overrides the clone()
- * method.
- */
- private boolean cloneFromHttpSession = false;
-
- // private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
-
- private HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository();
-
- public HttpSessionContextIntegrationFilter() throws ServletException {
- super.setSecurityContextRepository(repo);
- }
-
- public boolean isCloneFromHttpSession() {
- return cloneFromHttpSession;
- }
-
- public void setCloneFromHttpSession(boolean cloneFromHttpSession) {
- this.cloneFromHttpSession = cloneFromHttpSession;
- repo.setCloneFromHttpSession(cloneFromHttpSession);
- }
-
- public boolean isAllowSessionCreation() {
- return allowSessionCreation;
- }
-
- public void setAllowSessionCreation(boolean allowSessionCreation) {
- this.allowSessionCreation = allowSessionCreation;
- repo.setAllowSessionCreation(allowSessionCreation);
- }
-
- protected Class extends SecurityContext> getContextClass() {
- return contextClass;
- }
-
- @SuppressWarnings("unchecked")
- public void setContextClass(Class secureContext) {
- this.contextClass = secureContext;
- repo.setSecurityContextClass(secureContext);
- }
-
- public boolean isForceEagerSessionCreation() {
- return forceEagerSessionCreation;
- }
-
- public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
- this.forceEagerSessionCreation = forceEagerSessionCreation;
- super.setForceEagerSessionCreation(forceEagerSessionCreation);
- }
-
- //~ Methods ========================================================================================================
-
- public void afterPropertiesSet() {
- if (forceEagerSessionCreation && !allowSessionCreation) {
- throw new IllegalArgumentException(
- "If using forceEagerSessionCreation, you must set allowSessionCreation to also be true");
- }
- }
-}
diff --git a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
index 89521f3615..227d8b76f5 100644
--- a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
+++ b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java
@@ -10,6 +10,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -91,8 +92,8 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
}
- requestResponseHolder.setResponse(new SaveToSessionResponseWrapper(response, request,
- httpSession != null, context.hashCode()));
+ requestResponseHolder.setResponse(
+ new SaveToSessionResponseWrapper(response, request, httpSession != null, context));
return context;
}
@@ -293,7 +294,8 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
private HttpServletRequest request;
private boolean httpSessionExistedAtStartOfRequest;
- private int contextHashBeforeChainExecution;
+ private SecurityContext contextBeforeExecution;
+ private Authentication authBeforeExecution;
/**
* Takes the parameters required to call saveContext() successfully in
@@ -303,17 +305,17 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
* @param httpSessionExistedAtStartOfRequest indicates whether there was a session in place before the
* filter chain executed. If this is true, and the session is found to be null, this indicates that it was
* invalidated during the request and a new session will now be created.
- * @param contextHashBeforeChainExecution the hashcode of the context before the filter chain executed.
- * The context will only be stored if it has a different hashcode, indicating that the context changed
- * during the request.
+ * @param context the context before the filter chain executed.
+ * The context will only be stored if it or its contents changed during the request.
*/
SaveToSessionResponseWrapper(HttpServletResponse response, HttpServletRequest request,
boolean httpSessionExistedAtStartOfRequest,
- int contextHashBeforeChainExecution) {
+ SecurityContext context) {
super(response, disableUrlRewriting);
this.request = request;
this.httpSessionExistedAtStartOfRequest = httpSessionExistedAtStartOfRequest;
- this.contextHashBeforeChainExecution = contextHashBeforeChainExecution;
+ this.contextBeforeExecution = context;
+ this.authBeforeExecution = context.getAuthentication();
}
/**
@@ -331,7 +333,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
// See SEC-776
if (authenticationTrustResolver.isAnonymous(context.getAuthentication())) {
if (logger.isDebugEnabled()) {
- logger.debug("SecurityContext contents are anonymous - context will not be stored in HttpSession. ");
+ logger.debug("SecurityContext contents are anonymous - context will not be stored in HttpSession.");
}
return;
}
@@ -343,12 +345,18 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo
}
// If HttpSession exists, store current SecurityContextHolder contents but only if
- // the SecurityContext has actually changed (see JIRA SEC-37)
- if (httpSession != null && context.hashCode() != contextHashBeforeChainExecution) {
- httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
-
- if (logger.isDebugEnabled()) {
- logger.debug("SecurityContext stored to HttpSession: '" + context + "'");
+ // the SecurityContext has actually changed in this thread (see JIRA SEC-37, SEC-1307)
+ if (httpSession != null) {
+ SecurityContext contextFromSession = (SecurityContext) httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY);
+
+ if (context != contextFromSession) {
+ if (context != contextBeforeExecution || context.getAuthentication() != authBeforeExecution) {
+ httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("SecurityContext stored to HttpSession: '" + context + "'");
+ }
+ }
}
}
}
diff --git a/web/src/test/java/org/springframework/security/web/context/HttpSessionContextIntegrationFilterTests.java b/web/src/test/java/org/springframework/security/web/context/HttpSessionContextIntegrationFilterTests.java
deleted file mode 100644
index 9be42a694f..0000000000
--- a/web/src/test/java/org/springframework/security/web/context/HttpSessionContextIntegrationFilterTests.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * 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.context;
-
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-
-import org.junit.Test;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.security.MockFilterConfig;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-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.core.context.SecurityContextImpl;
-
-/**
- * Tests {@link HttpSessionContextIntegrationFilter}.
- *
- * @author Ben Alex
- */
-@SuppressWarnings("deprecation")
-public class HttpSessionContextIntegrationFilterTests {
- // Build an Authentication object we simulate came from HttpSession
- private UsernamePasswordAuthenticationToken sessionPrincipal = new UsernamePasswordAuthenticationToken(
- "someone",
- "password",
- AuthorityUtils.createAuthorityList("SOME_ROLE"));
-
-
- //~ Methods ========================================================================================================
-
- private static void executeFilterInContainerSimulator(
- FilterConfig filterConfig, Filter filter, ServletRequest request,
- ServletResponse response, FilterChain filterChain)
- throws ServletException, IOException {
-// filter.init(filterConfig);
- filter.doFilter(request, response, filterChain);
-// filter.destroy();
- }
-
- @Test
- public void testDetectsIncompatibleSessionProperties() throws Exception {
- HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
-
- try {
- filter.setAllowSessionCreation(false);
- filter.setForceEagerSessionCreation(true);
- filter.afterPropertiesSet();
- fail("Shown have thrown IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- assertTrue(true);
- }
-
- filter.setAllowSessionCreation(true);
- filter.afterPropertiesSet();
- assertTrue(true);
- }
-
- @Test
- public void testDetectsMissingOrInvalidContext() throws Exception {
- HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
-
- try {
- filter.setContextClass(null);
- filter.afterPropertiesSet();
- fail("Shown have thrown IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- assertTrue(true);
- }
-
- try {
- filter.setContextClass(Integer.class);
- assertEquals(Integer.class, filter.getContextClass());
- filter.afterPropertiesSet();
- fail("Shown have thrown IllegalArgumentException");
- } catch (IllegalArgumentException expected) {
- assertTrue(true);
- }
- }
-
- @Test
- public void testExceptionWithinFilterChainStillClearsSecurityContextHolder() throws Exception {
-
- // Build a Context to store in HttpSession (simulating prior request)
- SecurityContext sc = new SecurityContextImpl();
- sc.setAuthentication(sessionPrincipal);
-
- // Build a mock request
- MockHttpServletRequest request = new MockHttpServletRequest();
- request.getSession().setAttribute(
- HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY,
- sc);
-
- MockHttpServletResponse response = new MockHttpServletResponse();
- FilterChain chain = new MockFilterChain(sessionPrincipal, null,
- new IOException());
-
- // Prepare filter
- HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
- filter.setContextClass(SecurityContextImpl.class);
- filter.afterPropertiesSet();
-
- // Execute filter
- try {
- executeFilterInContainerSimulator(new MockFilterConfig(), filter,
- request, response, chain);
- fail("We should have received the IOException thrown inside the filter chain here");
- } catch (IOException ioe) {
- assertTrue(true);
- }
-
- // Check the SecurityContextHolder is null, even though an exception was
- // thrown during chain
- assertEquals(new SecurityContextImpl(), SecurityContextHolder.getContext());
- assertNull("Should have cleared FILTER_APPLIED",
- request.getAttribute(HttpSessionContextIntegrationFilter.FILTER_APPLIED));
- }
-
- @Test
- public void testExistingContextContentsCopiedIntoContextHolderFromSessionAndChangesToContextCopiedBackToSession()
- throws Exception {
-
- // Build an Authentication object we simulate came from HttpSession
- UsernamePasswordAuthenticationToken updatedPrincipal = new UsernamePasswordAuthenticationToken(
- "someone",
- "password",
- AuthorityUtils.createAuthorityList("SOME_DIFFERENT_ROLE"));
-
- // Build a Context to store in HttpSession (simulating prior request)
- SecurityContext sc = new SecurityContextImpl();
- sc.setAuthentication(sessionPrincipal);
-
- // Build a mock request
- MockHttpServletRequest request = new MockHttpServletRequest();
- request.getSession().setAttribute(
- HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY,
- sc);
-
- MockHttpServletResponse response = new MockHttpServletResponse();
- FilterChain chain = new MockFilterChain(sessionPrincipal,
- updatedPrincipal, null);
-
- // Prepare filter
- HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
- filter.setContextClass(SecurityContextImpl.class);
- filter.afterPropertiesSet();
-
- // Execute filter
- executeFilterInContainerSimulator(new MockFilterConfig(), filter,
- request, response, chain);
-
- // Obtain new/update Authentication from HttpSession
- SecurityContext context = (SecurityContext) request.getSession().getAttribute(
- HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
- assertEquals(updatedPrincipal, ((SecurityContext) context).getAuthentication());
- }
-
- @Test
- public void testHttpSessionCreatedWhenContextHolderChanges() throws Exception {
- // Build an Authentication object we simulate our Authentication changed it to
- UsernamePasswordAuthenticationToken updatedPrincipal = new UsernamePasswordAuthenticationToken(
- "someone",
- "password",
- AuthorityUtils.createAuthorityList("SOME_ROLE"));
-
- // Build a mock request
- MockHttpServletRequest request = new MockHttpServletRequest();
- MockHttpServletResponse response = new MockHttpServletResponse();
- FilterChain chain = new MockFilterChain(null, updatedPrincipal, null);
-
- // Prepare filter
- HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
- filter.setContextClass(SecurityContextImpl.class);
- // don't call afterPropertiesSet to test case when Spring filter.afterPropertiesSet(); isn't called
-
- // Execute filter
- executeFilterInContainerSimulator(new MockFilterConfig(), filter, request, response, chain);
-
- // Obtain new/updated Authentication from HttpSession
- SecurityContext context = (SecurityContext) request.getSession(false).getAttribute(
- HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
- assertEquals(updatedPrincipal, ((SecurityContext) context).getAuthentication());
- }
-
- @Test
- public void testHttpSessionEagerlyCreatedWhenDirected() throws Exception {
- // Build a mock request
- MockHttpServletRequest request = new MockHttpServletRequest(null, null);
- MockHttpServletResponse response = new MockHttpServletResponse();
- FilterChain chain = new MockFilterChain(null, null, null);
-
- // Prepare filter
- HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
- filter.setContextClass(SecurityContextImpl.class);
- filter.setForceEagerSessionCreation(true); // non-default
- filter.afterPropertiesSet();
-
- // Execute filter
- executeFilterInContainerSimulator(new MockFilterConfig(), filter,
- request, response, chain);
-
- // Check the session is not null
- assertNotNull(request.getSession(false));
- }
-
- @Test
- public void testHttpSessionNotCreatedUnlessContextHolderChanges() throws Exception {
- // Build a mock request
- MockHttpServletRequest request = new MockHttpServletRequest(null, null);
- MockHttpServletResponse response = new MockHttpServletResponse();
- FilterChain chain = new MockFilterChain(null, null, null);
-
- // Prepare filter
- HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
- filter.setContextClass(SecurityContextImpl.class);
- filter.afterPropertiesSet();
-
- // Execute filter
- executeFilterInContainerSimulator(new MockFilterConfig(), filter,
- request, response, chain);
-
- // Check the session is null
- assertNull(request.getSession(false));
- }
-
- @Test
- public void testHttpSessionWithNonContextInWellKnownLocationIsOverwritten() throws Exception {
- // Build an Authentication object we simulate our Authentication changed it to
- UsernamePasswordAuthenticationToken updatedPrincipal = new UsernamePasswordAuthenticationToken(
- "someone",
- "password",
- AuthorityUtils.createAuthorityList("SOME_DIFFERENT_ROLE"));
-
- // Build a mock request
- MockHttpServletRequest request = new MockHttpServletRequest();
- request.getSession().setAttribute(
- HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY,
- "NOT_A_CONTEXT_OBJECT");
-
- MockHttpServletResponse response = new MockHttpServletResponse();
- FilterChain chain = new MockFilterChain(null, updatedPrincipal, null);
-
- // Prepare filter
- HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
- filter.setContextClass(SecurityContextImpl.class);
- filter.afterPropertiesSet();
-
- // Execute filter
- executeFilterInContainerSimulator(new MockFilterConfig(), filter, request, response, chain);
-
- // Obtain new/update Authentication from HttpSession
- SecurityContext context = (SecurityContext) request.getSession().getAttribute(
- HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
- assertEquals(updatedPrincipal, ((SecurityContext) context).getAuthentication());
- }
-
- //~ Inner Classes ==================================================================================================
-
- private class MockFilterChain implements FilterChain {
- private Authentication changeContextHolder;
- private Authentication expectedOnContextHolder;
- private IOException toThrowDuringChain;
-
- public MockFilterChain(Authentication expectedOnContextHolder,
- Authentication changeContextHolder,
- IOException toThrowDuringChain) {
- this.expectedOnContextHolder = expectedOnContextHolder;
- this.changeContextHolder = changeContextHolder;
- this.toThrowDuringChain = toThrowDuringChain;
- }
-
- public void doFilter(ServletRequest arg0, ServletResponse arg1) throws IOException, ServletException {
- if (expectedOnContextHolder != null) {
- assertEquals(expectedOnContextHolder, SecurityContextHolder.getContext().getAuthentication());
- }
-
- if (changeContextHolder != null) {
- SecurityContext sc = SecurityContextHolder.getContext();
- sc.setAuthentication(changeContextHolder);
- SecurityContextHolder.setContext(sc);
- }
-
- if (toThrowDuringChain != null) {
- throw toThrowDuringChain;
- }
-
- }
- }
-}