Replaced massive if/else with guard clause to reduce nesting. Moved declaration of filterApplied boolean to where it is actually set. It is only used when removing the attribute from the request at the end of the invocation, so should probably not be needed at all. request.removeAttribute() can be called regardless of whether the attribute is set or not.

This commit is contained in:
Luke Taylor 2007-08-28 18:26:04 +00:00
parent 6fe00b3433
commit 6651a240de

View File

@ -36,8 +36,8 @@ import org.springframework.util.ReflectionUtils;
/** /**
* Populates the {@link SecurityContextHolder} with information obtained from * Populates the {@link SecurityContextHolder} with information obtained from
* the <code>HttpSession</code>. * the <code>HttpSession</code>.
* * <p/>
* <p> * <p/>
* The <code>HttpSession</code> will be queried to retrieve the * The <code>HttpSession</code> will be queried to retrieve the
* <code>SecurityContext</code> that should be stored against the * <code>SecurityContext</code> that should be stored against the
* <code>SecurityContextHolder</code> for the duration of the web request. At * <code>SecurityContextHolder</code> for the duration of the web request. At
@ -45,7 +45,7 @@ import org.springframework.util.ReflectionUtils;
* <code>SecurityContextHolder</code> will be persisted back to the * <code>SecurityContextHolder</code> will be persisted back to the
* <code>HttpSession</code> by this filter. * <code>HttpSession</code> by this filter.
* </p> * </p>
* <p> * <p/>
* If a valid <code>SecurityContext</code> cannot be obtained from the * If a valid <code>SecurityContext</code> cannot be obtained from the
* <code>HttpSession</code> for whatever reason, a fresh * <code>HttpSession</code> for whatever reason, a fresh
* <code>SecurityContext</code> will be created and used instead. The created * <code>SecurityContext</code> will be created and used instead. The created
@ -53,7 +53,7 @@ import org.springframework.util.ReflectionUtils;
* method (which defaults to {@link * method (which defaults to {@link
* org.acegisecurity.context.SecurityContextImpl}. * org.acegisecurity.context.SecurityContextImpl}.
* </p> * </p>
* <p> * <p/>
* No <code>HttpSession</code> will be created by this filter if one does not * No <code>HttpSession</code> will be created by this filter if one does not
* already exist. If at the end of the web request the <code>HttpSession</code> * already exist. If at the end of the web request the <code>HttpSession</code>
* does not exist, a <code>HttpSession</code> will <b>only</b> be created if * does not exist, a <code>HttpSession</code> will <b>only</b> be created if
@ -67,11 +67,11 @@ import org.springframework.util.ReflectionUtils;
* irrespective of normal session-minimisation logic (the default is * irrespective of normal session-minimisation logic (the default is
* <code>false</code>, as this is resource intensive and not recommended). * <code>false</code>, as this is resource intensive and not recommended).
* </p> * </p>
* <p> * <p/>
* This filter will only execute once per request, to resolve servlet container * This filter will only execute once per request, to resolve servlet container
* (specifically Weblogic) incompatibilities. * (specifically Weblogic) incompatibilities.
* </p> * </p>
* <p> * <p/>
* If for whatever reason no <code>HttpSession</code> should <b>ever</b> be * If for whatever reason no <code>HttpSession</code> should <b>ever</b> be
* created (eg this filter is only being used with Basic authentication or * created (eg this filter is only being used with Basic authentication or
* similar clients that will never present the same <code>jsessionid</code> * similar clients that will never present the same <code>jsessionid</code>
@ -84,7 +84,7 @@ import org.springframework.util.ReflectionUtils;
* <code>true</code> (setting it to <code>false</code> will cause a startup * <code>true</code> (setting it to <code>false</code> will cause a startup
* time error). * time error).
* </p> * </p>
* <p> * <p/>
* This filter MUST be executed BEFORE any authentication processing mechanisms. * This filter MUST be executed BEFORE any authentication processing mechanisms.
* Authentication processing mechanisms (eg BASIC, CAS processing filters etc) * Authentication processing mechanisms (eg BASIC, CAS processing filters etc)
* expect the <code>SecurityContextHolder</code> to contain a valid * expect the <code>SecurityContextHolder</code> to contain a valid
@ -94,317 +94,312 @@ import org.springframework.util.ReflectionUtils;
* @author Ben Alex * @author Ben Alex
* @author Patrick Burleson * @author Patrick Burleson
* @version $Id: HttpSessionContextIntegrationFilter.java 1784 2007-02-24 * @version $Id: HttpSessionContextIntegrationFilter.java 1784 2007-02-24
* 21:00:24Z luke_t $ * 21:00:24Z luke_t $
*/ */
public class HttpSessionContextIntegrationFilter implements InitializingBean, Filter { public class HttpSessionContextIntegrationFilter implements InitializingBean, Filter {
// ~ Static fields/initializers // ~ Static fields/initializers
// ===================================================================================== // =====================================================================================
protected static final Log logger = LogFactory.getLog(HttpSessionContextIntegrationFilter.class); protected static final Log logger = LogFactory.getLog(HttpSessionContextIntegrationFilter.class);
static final String FILTER_APPLIED = "__acegi_session_integration_filter_applied"; static final String FILTER_APPLIED = "__acegi_session_integration_filter_applied";
public static final String ACEGI_SECURITY_CONTEXT_KEY = "ACEGI_SECURITY_CONTEXT"; public static final String ACEGI_SECURITY_CONTEXT_KEY = "ACEGI_SECURITY_CONTEXT";
// ~ Instance fields // ~ Instance fields
// ================================================================================================ // ================================================================================================
private Class context = SecurityContextImpl.class; private Class context = SecurityContextImpl.class;
private Object contextObject; private Object contextObject;
/** /**
* Indicates if this filter can create a <code>HttpSession</code> if * Indicates if this filter can create a <code>HttpSession</code> if
* needed (sessions are always created sparingly, but setting this value to * needed (sessions are always created sparingly, but setting this value to
* <code>false</code> will prohibit sessions from ever being created). * <code>false</code> will prohibit sessions from ever being created).
* Defaults to <code>true</code>. Do not set to <code>false</code> if * Defaults to <code>true</code>. Do not set to <code>false</code> if
* you are have set {@link #forceEagerSessionCreation} to <code>true</code>, * you are have set {@link #forceEagerSessionCreation} to <code>true</code>,
* as the properties would be in conflict. * as the properties would be in conflict.
*/ */
private boolean allowSessionCreation = true; private boolean allowSessionCreation = true;
/** /**
* Indicates if this filter is required to create a <code>HttpSession</code> * Indicates if this filter is required to create a <code>HttpSession</code>
* for every request before proceeding through the filter chain, even if the * for every request before proceeding through the filter chain, even if the
* <code>HttpSession</code> would not ordinarily have been created. By * <code>HttpSession</code> would not ordinarily have been created. By
* default this is <code>false</code>, which is entirely appropriate for * default this is <code>false</code>, which is entirely appropriate for
* most circumstances as you do not want a <code>HttpSession</code> * most circumstances as you do not want a <code>HttpSession</code>
* created unless the filter actually needs one. It is envisaged the main * created unless the filter actually needs one. It is envisaged the main
* situation in which this property would be set to <code>true</code> is * situation in which this property would be set to <code>true</code> is
* if using other filters that depend on a <code>HttpSession</code> * if using other filters that depend on a <code>HttpSession</code>
* already existing, such as those which need to obtain a session ID. This * already existing, such as those which need to obtain a session ID. This
* is only required in specialised cases, so leave it set to * is only required in specialised cases, so leave it set to
* <code>false</code> unless you have an actual requirement and are * <code>false</code> unless you have an actual requirement and are
* conscious of the session creation overhead. * conscious of the session creation overhead.
*/ */
private boolean forceEagerSessionCreation = false; private boolean forceEagerSessionCreation = false;
/** /**
* Indicates whether the <code>SecurityContext</code> will be cloned from * Indicates whether the <code>SecurityContext</code> will be cloned from
* the <code>HttpSession</code>. The default is to simply reference (ie * the <code>HttpSession</code>. The default is to simply reference (ie
* the default is <code>false</code>). The default may cause issues if * the default is <code>false</code>). The default may cause issues if
* concurrent threads need to have a different security identity from other * concurrent threads need to have a different security identity from other
* threads being concurrently processed that share the same * threads being concurrently processed that share the same
* <code>HttpSession</code>. In most normal environments this does not * <code>HttpSession</code>. In most normal environments this does not
* represent an issue, as changes to the security identity in one thread is * represent an issue, as changes to the security identity in one thread is
* allowed to affect the security identitiy in other threads associated with * allowed to affect the security identitiy in other threads associated with
* the same <code>HttpSession</code>. For unusual cases where this is not * the same <code>HttpSession</code>. For unusual cases where this is not
* permitted, change this value to <code>true</code> and ensure the * permitted, change this value to <code>true</code> and ensure the
* {@link #context} is set to a <code>SecurityContext</code> that * {@link #context} is set to a <code>SecurityContext</code> that
* implements {@link Cloneable} and overrides the <code>clone()</code> * implements {@link Cloneable} and overrides the <code>clone()</code>
* method. * method.
*/ */
private boolean cloneFromHttpSession = false; private boolean cloneFromHttpSession = false;
public boolean isCloneFromHttpSession() { public boolean isCloneFromHttpSession() {
return cloneFromHttpSession; return cloneFromHttpSession;
} }
public void setCloneFromHttpSession(boolean cloneFromHttpSession) { public void setCloneFromHttpSession(boolean cloneFromHttpSession) {
this.cloneFromHttpSession = cloneFromHttpSession; this.cloneFromHttpSession = cloneFromHttpSession;
} }
public HttpSessionContextIntegrationFilter() throws ServletException { public HttpSessionContextIntegrationFilter() throws ServletException {
this.contextObject = generateNewContext(); this.contextObject = generateNewContext();
} }
// ~ Methods // ~ Methods
// ======================================================================================================== // ========================================================================================================
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
if ((this.context == null) || (!SecurityContext.class.isAssignableFrom(this.context))) { if ((this.context == null) || (!SecurityContext.class.isAssignableFrom(this.context))) {
throw new IllegalArgumentException("context must be defined and implement SecurityContext " throw new IllegalArgumentException("context must be defined and implement SecurityContext "
+ "(typically use org.acegisecurity.context.SecurityContextImpl; existing class is " + this.context + "(typically use org.acegisecurity.context.SecurityContextImpl; existing class is " + this.context
+ ")"); + ")");
} }
if ((forceEagerSessionCreation == true) && (allowSessionCreation == false)) { if ((forceEagerSessionCreation == true) && (allowSessionCreation == false)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"If using forceEagerSessionCreation, you must set allowSessionCreation to also be true"); "If using forceEagerSessionCreation, you must set allowSessionCreation to also be true");
} }
} }
/** /**
* Does nothing. We use IoC container lifecycle services instead. * Does nothing. We use IoC container lifecycle services instead.
*/ */
public void destroy() { public void destroy() {
} }
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException { ServletException {
boolean filterApplied = false; if ((request != null) && (request.getAttribute(FILTER_APPLIED) != null)) {
if ((request != null) && (request.getAttribute(FILTER_APPLIED) != null)) { // ensure that filter is only applied once per request
// ensure that filter is only applied once per request chain.doFilter(request, response);
chain.doFilter(request, response);
}
else {
HttpSession httpSession = null;
boolean httpSessionExistedAtStartOfRequest = false;
try { return;
httpSession = ((HttpServletRequest) request).getSession(forceEagerSessionCreation); }
}
catch (IllegalStateException ignored) {
}
if (httpSession != null) { HttpSession httpSession = null;
httpSessionExistedAtStartOfRequest = true; boolean httpSessionExistedAtStartOfRequest = false;
Object contextFromSessionObject = httpSession.getAttribute(ACEGI_SECURITY_CONTEXT_KEY); try {
httpSession = ((HttpServletRequest) request).getSession(forceEagerSessionCreation);
}
catch (IllegalStateException ignored) {
}
if (contextFromSessionObject != null) { if (httpSession != null) {
// Clone if required (see SEC-356) httpSessionExistedAtStartOfRequest = true;
if (cloneFromHttpSession) {
Assert.isInstanceOf(Cloneable.class, contextFromSessionObject,
"Context must implement Clonable and provide a Object.clone() method");
try {
Method m = contextFromSessionObject.getClass().getMethod("clone", new Class[] {});
if (!m.isAccessible()) {
m.setAccessible(true);
}
contextFromSessionObject = m.invoke(contextFromSessionObject, new Object[] {});
}
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
if (contextFromSessionObject instanceof SecurityContext) { Object contextFromSessionObject = httpSession.getAttribute(ACEGI_SECURITY_CONTEXT_KEY);
if (logger.isDebugEnabled()) {
logger.debug("Obtained from ACEGI_SECURITY_CONTEXT a valid SecurityContext and "
+ "set to SecurityContextHolder: '" + contextFromSessionObject + "'");
}
SecurityContextHolder.setContext((SecurityContext) contextFromSessionObject); if (contextFromSessionObject != null) {
} // Clone if required (see SEC-356)
else { if (cloneFromHttpSession) {
if (logger.isWarnEnabled()) { Assert.isInstanceOf(Cloneable.class, contextFromSessionObject,
logger "Context must implement Clonable and provide a Object.clone() method");
.warn("ACEGI_SECURITY_CONTEXT did not contain a SecurityContext but contained: '" try {
+ contextFromSessionObject Method m = contextFromSessionObject.getClass().getMethod("clone", new Class[]{});
+ "'; are you improperly modifying the HttpSession directly " if (!m.isAccessible()) {
+ "(you should always use SecurityContextHolder) or using the HttpSession attribute " m.setAccessible(true);
+ "reserved for this class? - new SecurityContext instance associated with " }
+ "SecurityContextHolder"); contextFromSessionObject = m.invoke(contextFromSessionObject, new Object[]{});
} }
catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
SecurityContextHolder.setContext(generateNewContext()); if (contextFromSessionObject instanceof SecurityContext) {
} if (logger.isDebugEnabled()) {
} logger.debug("Obtained from ACEGI_SECURITY_CONTEXT a valid SecurityContext and "
else { + "set to SecurityContextHolder: '" + contextFromSessionObject + "'");
if (logger.isDebugEnabled()) { }
logger.debug("HttpSession returned null object for ACEGI_SECURITY_CONTEXT - new "
+ "SecurityContext instance associated with SecurityContextHolder");
}
SecurityContextHolder.setContext(generateNewContext()); SecurityContextHolder.setContext((SecurityContext) contextFromSessionObject);
} } else {
} if (logger.isWarnEnabled()) {
else { logger
if (logger.isDebugEnabled()) { .warn("ACEGI_SECURITY_CONTEXT did not contain a SecurityContext but contained: '"
logger.debug("No HttpSession currently exists - new SecurityContext instance " + contextFromSessionObject
+ "associated with SecurityContextHolder"); + "'; are you improperly modifying the HttpSession directly "
} + "(you should always use SecurityContextHolder) or using the HttpSession attribute "
+ "reserved for this class? - new SecurityContext instance associated with "
+ "SecurityContextHolder");
}
SecurityContextHolder.setContext(generateNewContext()); SecurityContextHolder.setContext(generateNewContext());
} }
} else {
if (logger.isDebugEnabled()) {
logger.debug("HttpSession returned null object for ACEGI_SECURITY_CONTEXT - new "
+ "SecurityContext instance associated with SecurityContextHolder");
}
// Make the HttpSession null, as we want to ensure we don't keep SecurityContextHolder.setContext(generateNewContext());
// a reference to the HttpSession laying around in case the }
// chain.doFilter() invalidates it. } else {
httpSession = null; if (logger.isDebugEnabled()) {
logger.debug("No HttpSession currently exists - new SecurityContext instance "
// Proceed with chain + "associated with SecurityContextHolder");
int contextWhenChainProceeded = SecurityContextHolder.getContext().hashCode();
try {
filterApplied = true;
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
chain.doFilter(request, response);
} catch (IOException ioe) {
throw ioe;
} catch (ServletException se) {
throw se;
} }
finally {
// do clean up, even if there was an exception
// Store context back to HttpSession
try {
httpSession = ((HttpServletRequest) request).getSession(false);
}
catch (IllegalStateException ignored) {
}
if ((httpSession == null) && httpSessionExistedAtStartOfRequest) { SecurityContextHolder.setContext(generateNewContext());
if (logger.isDebugEnabled()) { }
logger.debug("HttpSession is now null, but was not null at start of request; "
+ "session was invalidated, so do not create a new session");
}
}
// Generate a HttpSession only if we need to // Make the HttpSession null, as we want to ensure we don't keep
if ((httpSession == null) && !httpSessionExistedAtStartOfRequest) { // a reference to the HttpSession laying around in case the
if (!allowSessionCreation) { // chain.doFilter() invalidates it.
if (logger.isDebugEnabled()) { httpSession = null;
logger
.debug("The HttpSession is currently null, and the "
+ "HttpSessionContextIntegrationFilter is prohibited from creating an HttpSession "
+ "(because the allowSessionCreation property is false) - SecurityContext thus not "
+ "stored for next request");
}
}
else if (!contextObject.equals(SecurityContextHolder.getContext())) {
if (logger.isDebugEnabled()) {
logger.debug("HttpSession being created as SecurityContextHolder contents are non-default");
}
try { // Proceed with chain
httpSession = ((HttpServletRequest) request).getSession(true); int contextWhenChainProceeded = SecurityContextHolder.getContext().hashCode();
} boolean filterApplied = true;
catch (IllegalStateException ignored) {
}
}
else {
if (logger.isDebugEnabled()) {
logger
.debug("HttpSession is null, but SecurityContextHolder has not changed from default: ' "
+ SecurityContextHolder.getContext()
+ "'; not creating HttpSession or storing SecurityContextHolder contents");
}
}
}
// If HttpSession exists, store current request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
// SecurityContextHolder contents but only if
// SecurityContext has
// actually changed (see JIRA SEC-37)
if ((httpSession != null)
&& (SecurityContextHolder.getContext().hashCode() != contextWhenChainProceeded)) {
httpSession.setAttribute(ACEGI_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
if (logger.isDebugEnabled()) { try {
logger.debug("SecurityContext stored to HttpSession: '" + SecurityContextHolder.getContext() chain.doFilter(request, response);
+ "'"); } catch (IOException ioe) {
} throw ioe;
} } catch (ServletException se) {
throw se;
}
finally {
// do clean up, even if there was an exception
// Store context back to HttpSession
try {
httpSession = ((HttpServletRequest) request).getSession(false);
}
catch (IllegalStateException ignored) {
}
if (filterApplied) { if ((httpSession == null) && httpSessionExistedAtStartOfRequest) {
request.removeAttribute(FILTER_APPLIED); if (logger.isDebugEnabled()) {
} logger.debug("HttpSession is now null, but was not null at start of request; "
+ "session was invalidated, so do not create a new session");
}
}
// Remove SecurityContextHolder contents // Generate a HttpSession only if we need to
SecurityContextHolder.clearContext(); if ((httpSession == null) && !httpSessionExistedAtStartOfRequest) {
if (!allowSessionCreation) {
if (logger.isDebugEnabled()) {
logger
.debug("The HttpSession is currently null, and the "
+ "HttpSessionContextIntegrationFilter is prohibited from creating an HttpSession "
+ "(because the allowSessionCreation property is false) - SecurityContext thus not "
+ "stored for next request");
}
} else if (!contextObject.equals(SecurityContextHolder.getContext())) {
if (logger.isDebugEnabled()) {
logger.debug("HttpSession being created as SecurityContextHolder contents are non-default");
}
if (logger.isDebugEnabled()) { try {
logger.debug("SecurityContextHolder set to new context, as request processing completed"); httpSession = ((HttpServletRequest) request).getSession(true);
} }
} catch (IllegalStateException ignored) {
} }
} } else {
if (logger.isDebugEnabled()) {
logger
.debug("HttpSession is null, but SecurityContextHolder has not changed from default: ' "
+ SecurityContextHolder.getContext()
+ "'; not creating HttpSession or storing SecurityContextHolder contents");
}
}
}
public SecurityContext generateNewContext() throws ServletException { // If HttpSession exists, store current
try { // SecurityContextHolder contents but only if
return (SecurityContext) this.context.newInstance(); // SecurityContext has
} // actually changed (see JIRA SEC-37)
catch (InstantiationException ie) { if ((httpSession != null)
throw new ServletException(ie); && (SecurityContextHolder.getContext().hashCode() != contextWhenChainProceeded)) {
} httpSession.setAttribute(ACEGI_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
catch (IllegalAccessException iae) {
throw new ServletException(iae);
}
}
public Class getContext() { if (logger.isDebugEnabled()) {
return context; logger.debug("SecurityContext stored to HttpSession: '" + SecurityContextHolder.getContext()
} + "'");
}
}
/** if (filterApplied) {
* Does nothing. We use IoC container lifecycle services instead. request.removeAttribute(FILTER_APPLIED);
* }
* @param filterConfig ignored
*
* @throws ServletException ignored
*/
public void init(FilterConfig filterConfig) throws ServletException {
}
public boolean isAllowSessionCreation() { // Remove SecurityContextHolder contents
return allowSessionCreation; SecurityContextHolder.clearContext();
}
public boolean isForceEagerSessionCreation() { if (logger.isDebugEnabled()) {
return forceEagerSessionCreation; logger.debug("SecurityContextHolder set to new context, as request processing completed");
} }
}
}
public void setAllowSessionCreation(boolean allowSessionCreation) { public SecurityContext generateNewContext() throws ServletException {
this.allowSessionCreation = allowSessionCreation; try {
} return (SecurityContext) this.context.newInstance();
}
catch (InstantiationException ie) {
throw new ServletException(ie);
}
catch (IllegalAccessException iae) {
throw new ServletException(iae);
}
}
public void setContext(Class secureContext) { public Class getContext() {
this.context = secureContext; return context;
} }
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) { /**
this.forceEagerSessionCreation = forceEagerSessionCreation; * Does nothing. We use IoC container lifecycle services instead.
*
* @param filterConfig ignored
* @throws ServletException ignored
*/
public void init(FilterConfig filterConfig) throws ServletException {
}
public boolean isAllowSessionCreation() {
return allowSessionCreation;
}
public boolean isForceEagerSessionCreation() {
return forceEagerSessionCreation;
}
public void setAllowSessionCreation(boolean allowSessionCreation) {
this.allowSessionCreation = allowSessionCreation;
}
public void setContext(Class secureContext) {
this.context = secureContext;
}
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
this.forceEagerSessionCreation = forceEagerSessionCreation;
} }
} }