diff --git a/web/src/main/java/org/springframework/security/web/context/HttpSessionContextIntegrationFilter.java b/web/src/main/java/org/springframework/security/web/context/HttpSessionContextIntegrationFilter.java deleted file mode 100644 index 6b807e5b9b..0000000000 --- a/web/src/main/java/org/springframework/security/web/context/HttpSessionContextIntegrationFilter.java +++ /dev/null @@ -1,194 +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 javax.servlet.ServletException; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; - -/** - * Populates the {@link SecurityContextHolder} with information obtained from - * the 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. - *

- *

- * If a valid 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 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 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; - } - - } - } -}