View Javadoc

    1   /* Copyright 2004, 2005 Acegi Technology Pty Limited
    2    *
    3    * Licensed under the Apache License, Version 2.0 (the "License");
    4    * you may not use this file except in compliance with the License.
    5    * You may obtain a copy of the License at
    6    *
    7    *     http://www.apache.org/licenses/LICENSE-2.0
    8    *
    9    * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15
    16  package org.acegisecurity.context;
    17
    18  import org.apache.commons.logging.Log;
    19  import org.apache.commons.logging.LogFactory;
    20
    21  import org.springframework.beans.factory.InitializingBean;
    22
    23  import java.io.IOException;
    24
    25  import javax.servlet.Filter;
    26  import javax.servlet.FilterChain;
    27  import javax.servlet.FilterConfig;
    28  import javax.servlet.ServletException;
    29  import javax.servlet.ServletRequest;
    30  import javax.servlet.ServletResponse;
    31  import javax.servlet.http.HttpServletRequest;
    32  import javax.servlet.http.HttpSession;
    33
    34
    35  /***
    36   * <p>
    37   * Populates the {@link SecurityContextHolder}</code> with information obtained
    38   * from the <code>HttpSession</code>.
    39   * </p>
    40   *
    41   * <p>
    42   * The <code>HttpSession</code> will be queried to retrieve the
    43   * <code>SecurityContext</code> that should be stored against the
    44   * <code>SecurityContextHolder</code> for the duration of the web request. At
    45   * the end of the web request, any updates made to the
    46   * <code>SecurityContextHolder</code> will be persisted back to the
    47   * <code>HttpSession</code> by this filter.
    48   * </p>
    49   *
    50   * <p>
    51   * If a valid <code>SecurityContext</code> cannot be obtained from the
    52   * <code>HttpSession</code> for whatever reason, a fresh
    53   * <code>SecurityContext</code> will be created and used instead.  The created
    54   * object will be of the instance defined by the {@link #setContext(Class)}
    55   * method (which defaults to {@link
    56   * org.acegisecurity.context.SecurityContextImpl}.
    57   * </p>
    58   *
    59   * <p>
    60   * No <code>HttpSession</code> will be created by this filter if one does not
    61   * already exist. If at the end of the web request the
    62   * <code>HttpSession</code> does not exist, a <code>HttpSession</code> will
    63   * <b>only</b> be created if the current contents of the
    64   * <code>SecurityContextHolder</code> are not {@link
    65   * java.lang.Object#equals(java.lang.Object)} to a <code>new</code> instance
    66   * of {@link #setContext(Class)}. This avoids needless
    67   * <code>HttpSession</code> creation, but automates the storage of changes
    68   * made to the <code>SecurityContextHolder</code>.
    69   * </p>
    70   *
    71   * <p>
    72   * This filter will only execute once per request, to resolve servlet container
    73   * (specifically Weblogic) incompatibilities.
    74   * </p>
    75   *
    76   * <p>
    77   * If for whatever reason no <code>HttpSession</code> should <b>ever</b> be
    78   * created (eg this filter is only being used with Basic authentication or
    79   * similar clients that will never present the same <code>jsessionid</code>
    80   * etc), the  {@link #setAllowSessionCreation(boolean)} should be set to
    81   * <code>false</code>. Only do this if you really need to conserve server
    82   * memory and ensure all classes using the <code>SecurityContextHolder</code> are
    83   * designed to have no persistence of the <code>SecurityContext</code> between web
    84   * requests.
    85   * </p>
    86   *
    87   * <p>
    88   * This filter MUST be executed BEFORE any authentication procesing mechanisms.
    89   * Authentication processing mechanisms (eg BASIC, CAS processing filters etc)
    90   * expect the <code>SecurityContextHolder</code> to contain a valid
    91   * <code>SecurityContext</code> by the time they execute.
    92   * </p>
    93   *
    94   * @author Ben Alex
    95   * @author Patrick Burleson
    96   * @version $Id: HttpSessionContextIntegrationFilter.java,v 1.12 2005/11/17 00:55:49 benalex Exp $
    97   */
    98  public class HttpSessionContextIntegrationFilter implements InitializingBean,
    99      Filter {
    100     //~ Static fields/initializers =============================================
    101
    102     protected static final Log logger = LogFactory.getLog(HttpSessionContextIntegrationFilter.class);
    103     private static final String FILTER_APPLIED = "__acegi_session_integration_filter_applied";
    104     public static final String ACEGI_SECURITY_CONTEXT_KEY = "ACEGI_SECURITY_CONTEXT";
    105
    106     //~ Instance fields ========================================================
    107
    108     private Class context = SecurityContextImpl.class;
    109     private Object contextObject;
    110
    111     /***
    112      * Indicates if this filter can create a <code>HttpSession</code> if needed
    113      * (sessions are always created sparingly, but setting this value to false
    114      * will prohibit sessions from ever being created). Defaults to true.
    115      */
    116     private boolean allowSessionCreation = true;
    117
    118     //~ Methods ================================================================
    119
    120     public void setAllowSessionCreation(boolean allowSessionCreation) {
    121         this.allowSessionCreation = allowSessionCreation;
    122     }
    123
    124     public boolean isAllowSessionCreation() {
    125         return allowSessionCreation;
    126     }
    127
    128     public void setContext(Class secureContext) {
    129         this.context = secureContext;
    130     }
    131
    132     public Class getContext() {
    133         return context;
    134     }
    135
    136     public void afterPropertiesSet() throws Exception {
    137         if ((this.context == null)
    138             || (!SecurityContext.class.isAssignableFrom(this.context))) {
    139             throw new IllegalArgumentException(
    140                 "context must be defined and implement SecurityContext (typically use org.acegisecurity.context.SecurityContextImpl; existing class is "
    141                 + this.context + ")");
    142         }
    143
    144         this.contextObject = generateNewContext();
    145     }
    146
    147     /***
    148      * Does nothing. We use IoC container lifecycle services instead.
    149      */
    150     public void destroy() {}
    151
    152     public void doFilter(ServletRequest request, ServletResponse response,
    153         FilterChain chain) throws IOException, ServletException {
    154         if ((request != null) && (request.getAttribute(FILTER_APPLIED) != null)) {
    155             // ensure that filter is only applied once per request
    156             chain.doFilter(request, response);
    157         } else {
    158             if (request != null) {
    159                 request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
    160             }
    161
    162             HttpSession httpSession = null;
    163             boolean httpSessionExistedAtStartOfRequest = false;
    164
    165             try {
    166                 httpSession = ((HttpServletRequest) request).getSession(false);
    167             } catch (IllegalStateException ignored) {}
    168
    169             if (httpSession != null) {
    170                 httpSessionExistedAtStartOfRequest = true;
    171
    172                 Object contextFromSessionObject = httpSession.getAttribute(ACEGI_SECURITY_CONTEXT_KEY);
    173
    174                 if (contextFromSessionObject != null) {
    175                     if (contextFromSessionObject instanceof SecurityContext) {
    176                         if (logger.isDebugEnabled()) {
    177                             logger.debug(
    178                                 "Obtained from ACEGI_SECURITY_CONTEXT a valid SecurityContext and set to SecurityContextHolder: '"
    179                                 + contextFromSessionObject + "'");
    180                         }
    181
    182                         SecurityContextHolder.setContext((SecurityContext) contextFromSessionObject);
    183                     } else {
    184                         if (logger.isWarnEnabled()) {
    185                             logger.warn(
    186                                 "ACEGI_SECURITY_CONTEXT did not contain a SecurityContext but contained: '"
    187                                 + contextFromSessionObject
    188                                 + "'; 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");
    189                         }
    190
    191                         SecurityContextHolder.setContext(generateNewContext());
    192                     }
    193                 } else {
    194                     if (logger.isDebugEnabled()) {
    195                         logger.debug(
    196                             "HttpSession returned null object for ACEGI_SECURITY_CONTEXT - new SecurityContext instance associated with SecurityContextHolder");
    197                     }
    198
    199                     SecurityContextHolder.setContext(generateNewContext());
    200                 }
    201             } else {
    202                 if (logger.isDebugEnabled()) {
    203                     logger.debug(
    204                         "No HttpSession currently exists - new SecurityContext instance associated with SecurityContextHolder");
    205                 }
    206
    207                 SecurityContextHolder.setContext(generateNewContext());
    208             }
    209
    210             // Make the HttpSession null, as we want to ensure we don't keep
    211             // a reference to the HttpSession laying around in case the
    212             // chain.doFilter() invalidates it.
    213             httpSession = null;
    214
    215             // Proceed with chain
    216             int contextWhenChainProceeded = SecurityContextHolder.getContext()
    217                                                                  .hashCode();
    218
    219             try {
    220                 chain.doFilter(request, response);
    221             } catch (IOException ioe) {
    222                 throw ioe;
    223             } catch (ServletException se) {
    224                 throw se;
    225             } finally {
    226                 // do clean up, even if there was an exception
    227                 // Store context back to HttpSession
    228                 try {
    229                     httpSession = ((HttpServletRequest) request).getSession(false);
    230                 } catch (IllegalStateException ignored) {}
    231
    232                 if ((httpSession == null) && httpSessionExistedAtStartOfRequest) {
    233                     if (logger.isDebugEnabled()) {
    234                         logger.debug(
    235                             "HttpSession is now null, but was not null at start of request; session was invalidated, so do not create a new session");
    236                     }
    237                 }
    238
    239                 // Generate a HttpSession only if we need to
    240                 if ((httpSession == null)
    241                     && !httpSessionExistedAtStartOfRequest) {
    242                     if (!allowSessionCreation) {
    243                         if (logger.isDebugEnabled()) {
    244                             logger.debug(
    245                                 "The HttpSession is currently null, and the HttpSessionContextIntegrationFilter is prohibited from creating a HttpSession (because the allowSessionCreation property is false) - SecurityContext thus not stored for next request");
    246                         }
    247                     } else if (!contextObject.equals(
    248                             SecurityContextHolder.getContext())) {
    249                         if (logger.isDebugEnabled()) {
    250                             logger.debug(
    251                                 "HttpSession being created as SecurityContextHolder contents are non-default");
    252                         }
    253
    254                         try {
    255                             httpSession = ((HttpServletRequest) request)
    256                                 .getSession(true);
    257                         } catch (IllegalStateException ignored) {}
    258                     } else {
    259                         if (logger.isDebugEnabled()) {
    260                             logger.debug(
    261                                 "HttpSession is null, but SecurityContextHolder has not changed from default: ' "
    262                                 + SecurityContextHolder.getContext()
    263                                 + "'; not creating HttpSession or storing SecurityContextHolder contents");
    264                         }
    265                     }
    266                 }
    267
    268                 // If HttpSession exists, store current SecurityContextHolder contents
    269                 // but only if SecurityContext has actually changed (see JIRA SEC-37)
    270                 if ((httpSession != null)
    271                     && (SecurityContextHolder.getContext().hashCode() != contextWhenChainProceeded)) {
    272                     httpSession.setAttribute(ACEGI_SECURITY_CONTEXT_KEY,
    273                         SecurityContextHolder.getContext());
    274
    275                     if (logger.isDebugEnabled()) {
    276                         logger.debug("SecurityContext stored to HttpSession: '"
    277                             + SecurityContextHolder.getContext() + "'");
    278                     }
    279                 }
    280
    281                 // Remove SecurityContextHolder contents
    282                 SecurityContextHolder.setContext(generateNewContext());
    283
    284                 if (logger.isDebugEnabled()) {
    285                     logger.debug(
    286                         "SecurityContextHolder set to new context, as request processing completed");
    287                 }
    288             }
    289         }
    290     }
    291
    292     public SecurityContext generateNewContext() throws ServletException {
    293         try {
    294             return (SecurityContext) this.context.newInstance();
    295         } catch (InstantiationException ie) {
    296             throw new ServletException(ie);
    297         } catch (IllegalAccessException iae) {
    298             throw new ServletException(iae);
    299         }
    300     }
    301
    302     /***
    303      * Does nothing. We use IoC container lifecycle services instead.
    304      *
    305      * @param filterConfig ignored
    306      *
    307      * @throws ServletException ignored
    308      */
    309     public void init(FilterConfig filterConfig) throws ServletException {}
    310 }