SEC-398: Added patch which uses response wrapper to set context in session on redirect or error.
This commit is contained in:
parent
219b865c01
commit
b2799985f2
|
@ -25,6 +25,8 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
@ -50,8 +52,7 @@ import org.springframework.util.ReflectionUtils;
|
|||
* <code>HttpSession</code> for whatever reason, a fresh
|
||||
* <code>SecurityContext</code> will be created and used instead. The created
|
||||
* object will be of the instance defined by the {@link #setContext(Class)}
|
||||
* method (which defaults to {@link
|
||||
* org.acegisecurity.context.SecurityContextImpl}.
|
||||
* method (which defaults to {@link org.acegisecurity.context.SecurityContextImpl}.
|
||||
* </p>
|
||||
* <p/>
|
||||
* No <code>HttpSession</code> will be created by this filter if one does not
|
||||
|
@ -93,6 +94,9 @@ import org.springframework.util.ReflectionUtils;
|
|||
*
|
||||
* @author Ben Alex
|
||||
* @author Patrick Burleson
|
||||
* @author Luke Taylor
|
||||
* @author Martin Algesten
|
||||
*
|
||||
* @version $Id$
|
||||
*/
|
||||
public class HttpSessionContextIntegrationFilter implements InitializingBean, Filter {
|
||||
|
@ -180,12 +184,6 @@ public class HttpSessionContextIntegrationFilter implements InitializingBean, Fi
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing. We use IoC container lifecycle services instead.
|
||||
*/
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
|
||||
ServletException {
|
||||
if (request.getAttribute(FILTER_APPLIED) != null) {
|
||||
|
@ -227,13 +225,21 @@ public class HttpSessionContextIntegrationFilter implements InitializingBean, Fi
|
|||
int contextHashBeforeChainExecution = contextBeforeChainExecution.hashCode();
|
||||
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
|
||||
|
||||
// Create a wrapper that will eagerly update the session with the security context
|
||||
// if anything in the chain does a sendError() or sendRedirect().
|
||||
// See SEC-398
|
||||
|
||||
OnRedirectUpdateSessionResponseWrapper responseWrapper =
|
||||
new OnRedirectUpdateSessionResponseWrapper( (HttpServletResponse)response, request,
|
||||
httpSessionExistedAtStartOfRequest, contextHashBeforeChainExecution );
|
||||
|
||||
// Proceed with chain
|
||||
|
||||
try {
|
||||
// This is the only place in this class where SecurityContextHolder.setContext() is called
|
||||
SecurityContextHolder.setContext(contextBeforeChainExecution);
|
||||
|
||||
chain.doFilter(request, response);
|
||||
chain.doFilter(request, responseWrapper);
|
||||
}
|
||||
finally {
|
||||
// This is the only place in this class where SecurityContextHolder.getContext() is called
|
||||
|
@ -244,8 +250,13 @@ public class HttpSessionContextIntegrationFilter implements InitializingBean, Fi
|
|||
|
||||
request.removeAttribute(FILTER_APPLIED);
|
||||
|
||||
storeSecurityContextInSession(contextAfterChainExecution, request,
|
||||
httpSessionExistedAtStartOfRequest, contextHashBeforeChainExecution);
|
||||
// storeSecurityContextInSession() might already be called by the response wrapper
|
||||
// if something in the chain called sendError() or sendRedirect(). This ensures we only call it
|
||||
// once per request.
|
||||
if ( !responseWrapper.isSessionUpdateDone() ) {
|
||||
storeSecurityContextInSession(contextAfterChainExecution, request,
|
||||
httpSessionExistedAtStartOfRequest, contextHashBeforeChainExecution);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("SecurityContextHolder now cleared, as request processing completed");
|
||||
|
@ -331,7 +342,7 @@ public class HttpSessionContextIntegrationFilter implements InitializingBean, Fi
|
|||
* @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 contextHashWhenChainProceeded the hashcode of the context before the filter chain executed.
|
||||
* @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.
|
||||
*
|
||||
|
@ -339,7 +350,7 @@ public class HttpSessionContextIntegrationFilter implements InitializingBean, Fi
|
|||
private void storeSecurityContextInSession(SecurityContext securityContext,
|
||||
ServletRequest request,
|
||||
boolean httpSessionExistedAtStartOfRequest,
|
||||
int contextHashWhenChainProceeded) {
|
||||
int contextHashBeforeChainExecution) {
|
||||
HttpSession httpSession = null;
|
||||
|
||||
try {
|
||||
|
@ -386,7 +397,7 @@ public class HttpSessionContextIntegrationFilter implements InitializingBean, Fi
|
|||
|
||||
// If HttpSession exists, store current SecurityContextHolder contents but only if
|
||||
// the SecurityContext has actually changed (see JIRA SEC-37)
|
||||
if (httpSession != null && securityContext.hashCode() != contextHashWhenChainProceeded) {
|
||||
if (httpSession != null && securityContext.hashCode() != contextHashBeforeChainExecution) {
|
||||
httpSession.setAttribute(ACEGI_SECURITY_CONTEXT_KEY, securityContext);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
@ -407,10 +418,6 @@ public class HttpSessionContextIntegrationFilter implements InitializingBean, Fi
|
|||
}
|
||||
}
|
||||
|
||||
public Class getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing. We use IoC container lifecycle services instead.
|
||||
*
|
||||
|
@ -420,23 +427,118 @@ public class HttpSessionContextIntegrationFilter implements InitializingBean, Fi
|
|||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
public boolean isAllowSessionCreation() {
|
||||
return allowSessionCreation;
|
||||
/**
|
||||
* Does nothing. We use IoC container lifecycle services instead.
|
||||
*/
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
public boolean isForceEagerSessionCreation() {
|
||||
return forceEagerSessionCreation;
|
||||
public boolean isAllowSessionCreation() {
|
||||
return allowSessionCreation;
|
||||
}
|
||||
|
||||
public void setAllowSessionCreation(boolean allowSessionCreation) {
|
||||
this.allowSessionCreation = allowSessionCreation;
|
||||
}
|
||||
|
||||
public Class getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public void setContext(Class secureContext) {
|
||||
this.context = secureContext;
|
||||
}
|
||||
|
||||
public boolean isForceEagerSessionCreation() {
|
||||
return forceEagerSessionCreation;
|
||||
}
|
||||
|
||||
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
|
||||
this.forceEagerSessionCreation = forceEagerSessionCreation;
|
||||
}
|
||||
|
||||
|
||||
//~ Inner Classes ==================================================================================================
|
||||
|
||||
/**
|
||||
* Wrapper that is applied to every request to update the <code>HttpSession<code> with
|
||||
* the <code>SecurityContext</code> when a <code>sendError()</code> or <code>sendRedirect</code>
|
||||
* happens. See SEC-398. The class contains the fields needed to call
|
||||
* <code>storeSecurityContextInSession()</code>
|
||||
*/
|
||||
private class OnRedirectUpdateSessionResponseWrapper extends HttpServletResponseWrapper {
|
||||
|
||||
ServletRequest request;
|
||||
boolean httpSessionExistedAtStartOfRequest;
|
||||
int contextHashBeforeChainExecution;
|
||||
|
||||
// Used to ensure storeSecurityContextInSession() is only
|
||||
// called once.
|
||||
boolean sessionUpdateDone = false;
|
||||
|
||||
/**
|
||||
* Takes the parameters required to call <code>storeSecurityContextInSession()</code> in
|
||||
* addition to the response object we are wrapping.
|
||||
* @see HttpSessionContextIntegrationFilter#storeSecurityContextInSession(SecurityContext, ServletRequest, boolean, int)
|
||||
*/
|
||||
public OnRedirectUpdateSessionResponseWrapper(HttpServletResponse response,
|
||||
ServletRequest request,
|
||||
boolean httpSessionExistedAtStartOfRequest,
|
||||
int contextHashBeforeChainExecution) {
|
||||
super(response);
|
||||
this.request = request;
|
||||
this.httpSessionExistedAtStartOfRequest = httpSessionExistedAtStartOfRequest;
|
||||
this.contextHashBeforeChainExecution = contextHashBeforeChainExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the session is updated before calling the
|
||||
* superclass <code>sendError()</code>
|
||||
*/
|
||||
public void sendError(int sc) throws IOException {
|
||||
doSessionUpdate();
|
||||
super.sendError(sc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the session is updated before calling the
|
||||
* superclass <code>sendError()</code>
|
||||
*/
|
||||
public void sendError(int sc, String msg) throws IOException {
|
||||
doSessionUpdate();
|
||||
super.sendError(sc, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the session is updated before calling the
|
||||
* superclass <code>sendRedirect()</code>
|
||||
*/
|
||||
public void sendRedirect(String location) throws IOException {
|
||||
doSessionUpdate();
|
||||
super.sendRedirect(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>storeSecurityContextInSession()</code>
|
||||
*/
|
||||
private void doSessionUpdate() {
|
||||
if (sessionUpdateDone) {
|
||||
return;
|
||||
}
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
storeSecurityContextInSession(securityContext, request,
|
||||
httpSessionExistedAtStartOfRequest, contextHashBeforeChainExecution);
|
||||
sessionUpdateDone = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the response wrapper has called
|
||||
* <code>storeSecurityContextInSession()</code>.
|
||||
*/
|
||||
public boolean isSessionUpdateDone() {
|
||||
return sessionUpdateDone;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,25 +39,22 @@ import javax.servlet.ServletResponse;
|
|||
|
||||
/**
|
||||
* Tests {@link HttpSessionContextIntegrationFilter}.
|
||||
*
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @version $Id: HttpSessionContextIntegrationFilterTests.java 1858 2007-05-24
|
||||
* 02:04:47Z benalex $
|
||||
*/
|
||||
public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
||||
// ~ Constructors
|
||||
// ===================================================================================================
|
||||
//~ Constructors ===================================================================================================
|
||||
|
||||
public HttpSessionContextIntegrationFilterTests() {
|
||||
super();
|
||||
}
|
||||
|
||||
public HttpSessionContextIntegrationFilterTests(String arg0) {
|
||||
super(arg0);
|
||||
}
|
||||
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
private static void executeFilterInContainerSimulator(
|
||||
FilterConfig filterConfig, Filter filter, ServletRequest request,
|
||||
|
@ -68,11 +65,6 @@ public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
|||
filter.destroy();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
junit.textui.TestRunner
|
||||
.run(HttpSessionContextIntegrationFilterTests.class);
|
||||
}
|
||||
|
||||
public void testDetectsIncompatibleSessionProperties() throws Exception {
|
||||
HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
|
||||
|
||||
|
@ -111,8 +103,7 @@ public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testExceptionWithinFilterChainStillClearsSecurityContextHolder()
|
||||
throws Exception {
|
||||
public void testExceptionWithinFilterChainStillClearsSecurityContextHolder() throws Exception {
|
||||
// Build an Authentication object we simulate came from HttpSession
|
||||
PrincipalAcegiUserToken sessionPrincipal = new PrincipalAcegiUserToken(
|
||||
"key",
|
||||
|
@ -151,12 +142,9 @@ public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
|||
|
||||
// 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));
|
||||
assertEquals(new SecurityContextImpl(), SecurityContextHolder.getContext());
|
||||
assertNull("Should have cleared FILTER_APPLIED",
|
||||
request.getAttribute(HttpSessionContextIntegrationFilter.FILTER_APPLIED));
|
||||
}
|
||||
|
||||
public void testExistingContextContentsCopiedIntoContextHolderFromSessionAndChangesToContextCopiedBackToSession()
|
||||
|
@ -200,18 +188,13 @@ public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
|||
request, response, chain);
|
||||
|
||||
// Obtain new/update Authentication from HttpSession
|
||||
SecurityContext context = (SecurityContext) request
|
||||
.getSession()
|
||||
.getAttribute(
|
||||
SecurityContext context = (SecurityContext) request.getSession().getAttribute(
|
||||
HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);
|
||||
assertEquals(updatedPrincipal, ((SecurityContext) context)
|
||||
.getAuthentication());
|
||||
assertEquals(updatedPrincipal, ((SecurityContext) context).getAuthentication());
|
||||
}
|
||||
|
||||
public void testHttpSessionCreatedWhenContextHolderChanges()
|
||||
throws Exception {
|
||||
// Build an Authentication object we simulate our Authentication changed
|
||||
// it to
|
||||
public void testHttpSessionCreatedWhenContextHolderChanges() throws Exception {
|
||||
// Build an Authentication object we simulate our Authentication changed it to
|
||||
PrincipalAcegiUserToken updatedPrincipal = new PrincipalAcegiUserToken(
|
||||
"key", "someone", "password",
|
||||
new GrantedAuthority[] { new GrantedAuthorityImpl(
|
||||
|
@ -225,21 +208,15 @@ public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
|||
// Prepare filter
|
||||
HttpSessionContextIntegrationFilter filter = new HttpSessionContextIntegrationFilter();
|
||||
filter.setContext(SecurityContextImpl.class);
|
||||
// don't call afterPropertiesSet to test case when not instantiated by
|
||||
// Spring
|
||||
// filter.afterPropertiesSet();
|
||||
// don't call afterPropertiesSet to test case when Spring filter.afterPropertiesSet(); isn't called
|
||||
|
||||
// Execute filter
|
||||
executeFilterInContainerSimulator(new MockFilterConfig(), filter,
|
||||
request, response, chain);
|
||||
executeFilterInContainerSimulator(new MockFilterConfig(), filter, request, response, chain);
|
||||
|
||||
// Obtain new/updated Authentication from HttpSession
|
||||
SecurityContext context = (SecurityContext) request
|
||||
.getSession(false)
|
||||
.getAttribute(
|
||||
SecurityContext context = (SecurityContext) request.getSession(false).getAttribute(
|
||||
HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);
|
||||
assertEquals(updatedPrincipal, ((SecurityContext) context)
|
||||
.getAuthentication());
|
||||
assertEquals(updatedPrincipal, ((SecurityContext) context).getAuthentication());
|
||||
}
|
||||
|
||||
public void testHttpSessionEagerlyCreatedWhenDirected() throws Exception {
|
||||
|
@ -262,8 +239,7 @@ public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
|||
assertNotNull(request.getSession(false));
|
||||
}
|
||||
|
||||
public void testHttpSessionNotCreatedUnlessContextHolderChanges()
|
||||
throws Exception {
|
||||
public void testHttpSessionNotCreatedUnlessContextHolderChanges() throws Exception {
|
||||
// Build a mock request
|
||||
MockHttpServletRequest request = new MockHttpServletRequest(null, null);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
@ -282,8 +258,7 @@ public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
|||
assertNull(request.getSession(false));
|
||||
}
|
||||
|
||||
public void testHttpSessionWithNonContextInWellKnownLocationIsOverwritten()
|
||||
throws Exception {
|
||||
public void testHttpSessionWithNonContextInWellKnownLocationIsOverwritten() throws Exception {
|
||||
// Build an Authentication object we simulate our Authentication changed
|
||||
// it to
|
||||
PrincipalAcegiUserToken updatedPrincipal = new PrincipalAcegiUserToken(
|
||||
|
@ -306,20 +281,15 @@ public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
|||
filter.afterPropertiesSet();
|
||||
|
||||
// Execute filter
|
||||
executeFilterInContainerSimulator(new MockFilterConfig(), filter,
|
||||
request, response, chain);
|
||||
executeFilterInContainerSimulator(new MockFilterConfig(), filter, request, response, chain);
|
||||
|
||||
// Obtain new/update Authentication from HttpSession
|
||||
SecurityContext context = (SecurityContext) request
|
||||
.getSession()
|
||||
.getAttribute(
|
||||
SecurityContext context = (SecurityContext) request.getSession().getAttribute(
|
||||
HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY);
|
||||
assertEquals(updatedPrincipal, ((SecurityContext) context)
|
||||
.getAuthentication());
|
||||
assertEquals(updatedPrincipal, ((SecurityContext) context).getAuthentication());
|
||||
}
|
||||
|
||||
public void testConcurrentThreadsLazilyChangeFilterAppliedValueToTrue()
|
||||
throws Exception {
|
||||
public void testConcurrentThreadsLazilyChangeFilterAppliedValueToTrue() throws Exception {
|
||||
PrincipalAcegiUserToken sessionPrincipal = new PrincipalAcegiUserToken(
|
||||
"key",
|
||||
"someone",
|
||||
|
@ -366,15 +336,9 @@ public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
|||
this.toThrowDuringChain = toThrowDuringChain;
|
||||
}
|
||||
|
||||
private MockFilterChain() {
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest arg0, ServletResponse arg1)
|
||||
throws IOException, ServletException {
|
||||
|
||||
public void doFilter(ServletRequest arg0, ServletResponse arg1) throws IOException, ServletException {
|
||||
if (expectedOnContextHolder != null) {
|
||||
assertEquals(expectedOnContextHolder, SecurityContextHolder
|
||||
.getContext().getAuthentication());
|
||||
assertEquals(expectedOnContextHolder, SecurityContextHolder.getContext().getAuthentication());
|
||||
}
|
||||
|
||||
if (changeContextHolder != null) {
|
||||
|
@ -409,8 +373,7 @@ public class HttpSessionContextIntegrationFilterTests extends TestCase {
|
|||
public void run() {
|
||||
try {
|
||||
// Execute filter
|
||||
executeFilterInContainerSimulator(new MockFilterConfig(),
|
||||
filter, request, response, chain);
|
||||
executeFilterInContainerSimulator(new MockFilterConfig(), filter, request, response, chain);
|
||||
|
||||
// Check the session is not null
|
||||
assertNotNull(request.getSession(false));
|
||||
|
|
Loading…
Reference in New Issue