Browse Source

SEC-423: Fixed IllegalArguemntException being thrown by checking for null contextFromSessionObject

1.0.x
Vishal Puri 19 years ago
parent
commit
62c832e366
  1. 654
      core/src/main/java/org/acegisecurity/context/HttpSessionContextIntegrationFilter.java

654
core/src/main/java/org/acegisecurity/context/HttpSessionContextIntegrationFilter.java

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

Loading…
Cancel
Save