Coverage Report - org.acegisecurity.context.HttpSessionContextIntegrationFilter
 
Classes in this File Line Coverage Branch Coverage Complexity
HttpSessionContextIntegrationFilter
64% 
84% 
2.955
 
 1  
 /* Copyright 2004, 2005, 2006 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 java.io.IOException;
 19  
 import java.lang.reflect.Method;
 20  
 
 21  
 import javax.servlet.Filter;
 22  
 import javax.servlet.FilterChain;
 23  
 import javax.servlet.FilterConfig;
 24  
 import javax.servlet.ServletException;
 25  
 import javax.servlet.ServletRequest;
 26  
 import javax.servlet.ServletResponse;
 27  
 import javax.servlet.http.HttpServletRequest;
 28  
 import javax.servlet.http.HttpServletResponse;
 29  
 import javax.servlet.http.HttpServletResponseWrapper;
 30  
 import javax.servlet.http.HttpSession;
 31  
 
 32  
 import org.apache.commons.logging.Log;
 33  
 import org.apache.commons.logging.LogFactory;
 34  
 import org.springframework.beans.factory.InitializingBean;
 35  
 import org.springframework.util.Assert;
 36  
 import org.springframework.util.ReflectionUtils;
 37  
 
 38  
 /**
 39  
  * Populates the {@link SecurityContextHolder} with information obtained from
 40  
  * the <code>HttpSession</code>.
 41  
  * <p/>
 42  
  * <p/>
 43  
  * The <code>HttpSession</code> will be queried to retrieve the
 44  
  * <code>SecurityContext</code> that should be stored against the
 45  
  * <code>SecurityContextHolder</code> for the duration of the web request. At
 46  
  * the end of the web request, any updates made to the
 47  
  * <code>SecurityContextHolder</code> will be persisted back to the
 48  
  * <code>HttpSession</code> by this filter.
 49  
  * </p>
 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 org.acegisecurity.context.SecurityContextImpl}.
 56  
  * </p>
 57  
  * <p/>
 58  
  * No <code>HttpSession</code> will be created by this filter if one does not
 59  
  * already exist. If at the end of the web request the <code>HttpSession</code>
 60  
  * does not exist, a <code>HttpSession</code> will <b>only</b> be created if
 61  
  * the current contents of the <code>SecurityContextHolder</code> are not
 62  
  * {@link java.lang.Object#equals(java.lang.Object)} to a <code>new</code>
 63  
  * instance of {@link #setContext(Class)}. This avoids needless
 64  
  * <code>HttpSession</code> creation, but automates the storage of changes
 65  
  * made to the <code>SecurityContextHolder</code>. There is one exception to
 66  
  * this rule, that is if the {@link #forceEagerSessionCreation} property is
 67  
  * <code>true</code>, in which case sessions will always be created
 68  
  * irrespective of normal session-minimisation logic (the default is
 69  
  * <code>false</code>, as this is resource intensive and not recommended).
 70  
  * </p>
 71  
  * <p/>
 72  
  * This filter will only execute once per request, to resolve servlet container
 73  
  * (specifically Weblogic) incompatibilities.
 74  
  * </p>
 75  
  * <p/>
 76  
  * If for whatever reason no <code>HttpSession</code> should <b>ever</b> be
 77  
  * created (eg this filter is only being used with Basic authentication or
 78  
  * similar clients that will never present the same <code>jsessionid</code>
 79  
  * etc), the {@link #setAllowSessionCreation(boolean)} should be set to
 80  
  * <code>false</code>. Only do this if you really need to conserve server
 81  
  * memory and ensure all classes using the <code>SecurityContextHolder</code>
 82  
  * are designed to have no persistence of the <code>SecurityContext</code>
 83  
  * between web requests. Please note that if {@link #forceEagerSessionCreation}
 84  
  * is <code>true</code>, the <code>allowSessionCreation</code> must also be
 85  
  * <code>true</code> (setting it to <code>false</code> will cause a startup
 86  
  * time error).
 87  
  * </p>
 88  
  * <p/>
 89  
  * This filter MUST be executed BEFORE any authentication processing mechanisms.
 90  
  * Authentication processing mechanisms (eg BASIC, CAS processing filters etc)
 91  
  * expect the <code>SecurityContextHolder</code> to contain a valid
 92  
  * <code>SecurityContext</code> by the time they execute.
 93  
  * </p>
 94  
  *
 95  
  * @author Ben Alex
 96  
  * @author Patrick Burleson
 97  
  * @author Luke Taylor
 98  
  * @author Martin Algesten
 99  
  *
 100  
  * @version $Id: HttpSessionContextIntegrationFilter.java 2004 2007-09-01 14:43:09Z raykrueger $
 101  
  */
 102  0
 public class HttpSessionContextIntegrationFilter implements InitializingBean, Filter {
 103  
     //~ Static fields/initializers =====================================================================================
 104  
 
 105  6
     protected static final Log logger = LogFactory.getLog(HttpSessionContextIntegrationFilter.class);
 106  
 
 107  
     static final String FILTER_APPLIED = "__acegi_session_integration_filter_applied";
 108  
 
 109  
     public static final String ACEGI_SECURITY_CONTEXT_KEY = "ACEGI_SECURITY_CONTEXT";
 110  
 
 111  
     //~ Instance fields ================================================================================================
 112  
 
 113  9
     private Class context = SecurityContextImpl.class;
 114  
 
 115  
     private Object contextObject;
 116  
 
 117  
     /**
 118  
      * Indicates if this filter can create a <code>HttpSession</code> if
 119  
      * needed (sessions are always created sparingly, but setting this value to
 120  
      * <code>false</code> will prohibit sessions from ever being created).
 121  
      * Defaults to <code>true</code>. Do not set to <code>false</code> if
 122  
      * you are have set {@link #forceEagerSessionCreation} to <code>true</code>,
 123  
      * as the properties would be in conflict.
 124  
      */
 125  9
     private boolean allowSessionCreation = true;
 126  
 
 127  
     /**
 128  
      * Indicates if this filter is required to create a <code>HttpSession</code>
 129  
      * for every request before proceeding through the filter chain, even if the
 130  
      * <code>HttpSession</code> would not ordinarily have been created. By
 131  
      * default this is <code>false</code>, which is entirely appropriate for
 132  
      * most circumstances as you do not want a <code>HttpSession</code>
 133  
      * created unless the filter actually needs one. It is envisaged the main
 134  
      * situation in which this property would be set to <code>true</code> is
 135  
      * if using other filters that depend on a <code>HttpSession</code>
 136  
      * already existing, such as those which need to obtain a session ID. This
 137  
      * is only required in specialised cases, so leave it set to
 138  
      * <code>false</code> unless you have an actual requirement and are
 139  
      * conscious of the session creation overhead.
 140  
      */
 141  9
     private boolean forceEagerSessionCreation = false;
 142  
 
 143  
     /**
 144  
      * Indicates whether the <code>SecurityContext</code> will be cloned from
 145  
      * the <code>HttpSession</code>. The default is to simply reference (ie
 146  
      * the default is <code>false</code>). The default may cause issues if
 147  
      * concurrent threads need to have a different security identity from other
 148  
      * threads being concurrently processed that share the same
 149  
      * <code>HttpSession</code>. In most normal environments this does not
 150  
      * represent an issue, as changes to the security identity in one thread is
 151  
      * allowed to affect the security identitiy in other threads associated with
 152  
      * the same <code>HttpSession</code>. For unusual cases where this is not
 153  
      * permitted, change this value to <code>true</code> and ensure the
 154  
      * {@link #context} is set to a <code>SecurityContext</code> that
 155  
      * implements {@link Cloneable} and overrides the <code>clone()</code>
 156  
      * method.
 157  
      */
 158  9
     private boolean cloneFromHttpSession = false;
 159  
 
 160  
     public boolean isCloneFromHttpSession() {
 161  0
         return cloneFromHttpSession;
 162  
     }
 163  
 
 164  
     public void setCloneFromHttpSession(boolean cloneFromHttpSession) {
 165  0
         this.cloneFromHttpSession = cloneFromHttpSession;
 166  0
     }
 167  
 
 168  9
     public HttpSessionContextIntegrationFilter() throws ServletException {
 169  9
         this.contextObject = generateNewContext();
 170  9
     }
 171  
 
 172  
     //~ Methods ========================================================================================================
 173  
 
 174  
     public void afterPropertiesSet() throws Exception {
 175  10
         if ((this.context == null) || (!SecurityContext.class.isAssignableFrom(this.context))) {
 176  2
             throw new IllegalArgumentException("context must be defined and implement SecurityContext "
 177  
                     + "(typically use org.acegisecurity.context.SecurityContextImpl; existing class is " + this.context
 178  
                     + ")");
 179  
         }
 180  
 
 181  8
         if (forceEagerSessionCreation && !allowSessionCreation) {
 182  1
             throw new IllegalArgumentException(
 183  
                     "If using forceEagerSessionCreation, you must set allowSessionCreation to also be true");
 184  
         }
 185  7
     }
 186  
 
 187  
     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
 188  
             ServletException {
 189  
 
 190  9
         Assert.isInstanceOf(HttpServletRequest.class, req, "ServletRequest must be an instance of HttpServletRequest");
 191  9
         Assert.isInstanceOf(HttpServletResponse.class, res, "ServletResponse must be an instance of HttpServletResponse");
 192  
 
 193  9
         HttpServletRequest request = (HttpServletRequest) req;
 194  9
         HttpServletResponse response = (HttpServletResponse) res;
 195  
 
 196  9
         if (request.getAttribute(FILTER_APPLIED) != null) {
 197  
             // ensure that filter is only applied once per request
 198  1
             chain.doFilter(request, response);
 199  
 
 200  0
             return;
 201  
         }
 202  
 
 203  8
         HttpSession httpSession = null;
 204  
 
 205  
         try {
 206  8
             httpSession = request.getSession(forceEagerSessionCreation);
 207  
         }
 208  0
         catch (IllegalStateException ignored) {
 209  8
         }
 210  
 
 211  8
         boolean httpSessionExistedAtStartOfRequest = httpSession != null;
 212  
 
 213  8
         SecurityContext contextBeforeChainExecution = readSecurityContextFromSession(httpSession);
 214  
 
 215  
         // Make the HttpSession null, as we don't want to keep a reference to it lying
 216  
         // around in case chain.doFilter() invalidates it.
 217  8
         httpSession = null;
 218  
 
 219  8
         if (contextBeforeChainExecution == null) {
 220  4
             contextBeforeChainExecution = generateNewContext();
 221  
 
 222  4
             if (logger.isDebugEnabled()) {
 223  0
                 logger.debug("New SecurityContext instance will be associated with SecurityContextHolder");
 224  
             }
 225  
         } else {
 226  4
             if (logger.isDebugEnabled()) {
 227  0
                 logger.debug("Obtained a valid SecurityContext from ACEGI_SECURITY_CONTEXT to "
 228  
                         + "associate with SecurityContextHolder: '" + contextBeforeChainExecution + "'");
 229  
             }
 230  
         }
 231  
 
 232  8
         int contextHashBeforeChainExecution = contextBeforeChainExecution.hashCode();
 233  8
         request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
 234  
 
 235  
         // Create a wrapper that will eagerly update the session with the security context
 236  
         // if anything in the chain does a sendError() or sendRedirect().
 237  
         // See SEC-398
 238  
 
 239  8
         OnRedirectUpdateSessionResponseWrapper responseWrapper =
 240  
                 new OnRedirectUpdateSessionResponseWrapper( response, request,
 241  
                     httpSessionExistedAtStartOfRequest, contextHashBeforeChainExecution );
 242  
 
 243  
         // Proceed with chain
 244  
 
 245  
         try {
 246  
             // This is the only place in this class where SecurityContextHolder.setContext() is called
 247  8
             SecurityContextHolder.setContext(contextBeforeChainExecution);
 248  
 
 249  8
             chain.doFilter(request, responseWrapper);
 250  
         }
 251  
         finally {
 252  
             // This is the only place in this class where SecurityContextHolder.getContext() is called
 253  8
             SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
 254  
 
 255  
             // Crucial removal of SecurityContextHolder contents - do this before anything else.
 256  8
             SecurityContextHolder.clearContext();
 257  
 
 258  8
             request.removeAttribute(FILTER_APPLIED);
 259  
 
 260  
             // storeSecurityContextInSession() might already be called by the response wrapper
 261  
             // if something in the chain called sendError() or sendRedirect(). This ensures we only call it
 262  
             // once per request.
 263  8
             if ( !responseWrapper.isSessionUpdateDone() ) {
 264  8
               storeSecurityContextInSession(contextAfterChainExecution, request,
 265  
                       httpSessionExistedAtStartOfRequest, contextHashBeforeChainExecution);
 266  
             }
 267  
 
 268  8
             if (logger.isDebugEnabled()) {
 269  0
                 logger.debug("SecurityContextHolder now cleared, as request processing completed");
 270  
             }
 271  8
         }
 272  7
     }
 273  
 
 274  
     /**
 275  
      * Gets the security context from the session (if available) and returns it.
 276  
      * <p/>
 277  
      * If the session is null, the context object is null or the context object stored in the session
 278  
      * is not an instance of SecurityContext it will return null.
 279  
      * <p/>
 280  
      * If <tt>cloneFromHttpSession</tt> is set to true, it will attempt to clone the context object
 281  
      * and return the cloned instance.
 282  
      *
 283  
      * @param httpSession the session obtained from the request.
 284  
      */
 285  
     private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
 286  8
         if (httpSession == null) {
 287  2
             if (logger.isDebugEnabled()) {
 288  0
                 logger.debug("No HttpSession currently exists");
 289  
             }
 290  
 
 291  2
             return null;
 292  
         }
 293  
 
 294  
         // Session exists, so try to obtain a context from it.
 295  
 
 296  6
         Object contextFromSessionObject = httpSession.getAttribute(ACEGI_SECURITY_CONTEXT_KEY);
 297  
 
 298  6
         if (contextFromSessionObject == null) {
 299  1
             if (logger.isDebugEnabled()) {
 300  0
                 logger.debug("HttpSession returned null object for ACEGI_SECURITY_CONTEXT");
 301  
             }
 302  
 
 303  1
             return null;
 304  
         }
 305  
 
 306  
         // We now have the security context object from the session.
 307  
 
 308  
         // Clone if required (see SEC-356)
 309  5
         if (cloneFromHttpSession) {
 310  0
             Assert.isInstanceOf(Cloneable.class, contextFromSessionObject,
 311  
                     "Context must implement Clonable and provide a Object.clone() method");
 312  
             try {
 313  0
                 Method m = contextFromSessionObject.getClass().getMethod("clone", new Class[]{});
 314  0
                 if (!m.isAccessible()) {
 315  0
                     m.setAccessible(true);
 316  
                 }
 317  0
                 contextFromSessionObject = m.invoke(contextFromSessionObject, new Object[]{});
 318  
             }
 319  0
             catch (Exception ex) {
 320  0
                 ReflectionUtils.handleReflectionException(ex);
 321  0
             }
 322  
         }
 323  
 
 324  5
         if (!(contextFromSessionObject instanceof SecurityContext)) {
 325  1
             if (logger.isWarnEnabled()) {
 326  1
                 logger.warn("ACEGI_SECURITY_CONTEXT did not contain a SecurityContext but contained: '"
 327  
                         + contextFromSessionObject
 328  
                         + "'; are you improperly modifying the HttpSession directly "
 329  
                         + "(you should always use SecurityContextHolder) or using the HttpSession attribute "
 330  
                         + "reserved for this class?");
 331  
             }
 332  
 
 333  1
             return null;
 334  
         }
 335  
 
 336  
         // Everything OK. The only non-null return from this method.
 337  
 
 338  4
         return (SecurityContext) contextFromSessionObject;
 339  
     }
 340  
 
 341  
     /**
 342  
      * Stores the supplied security context in the session (if available) and if it has changed since it was
 343  
      * set at the start of the request.
 344  
      *
 345  
      * @param securityContext the context object obtained from the SecurityContextHolder after the request has
 346  
      *        been processed by the filter chain. SecurityContextHolder.getContext() cannot be used to obtain
 347  
      *        the context as it has already been cleared by the time this method is called.
 348  
      * @param request the request object (used to obtain the session, if one exists).
 349  
      * @param httpSessionExistedAtStartOfRequest indicates whether there was a session in place before the
 350  
      *        filter chain executed. If this is true, and the session is found to be null, this indicates that it was
 351  
      *        invalidated during the request and a new session will now be created.
 352  
      * @param contextHashBeforeChainExecution the hashcode of the context before the filter chain executed.
 353  
      *        The context will only be stored if it has a different hashcode, indicating that the context changed
 354  
      *        during the request.
 355  
      *
 356  
      */
 357  
     private void storeSecurityContextInSession(SecurityContext securityContext,
 358  
                                                HttpServletRequest request,
 359  
                                                boolean httpSessionExistedAtStartOfRequest,
 360  
                                                int contextHashBeforeChainExecution) {
 361  8
         HttpSession httpSession = null;
 362  
 
 363  
         try {
 364  8
             httpSession = request.getSession(false);
 365  
         }
 366  0
         catch (IllegalStateException ignored) {
 367  8
         }
 368  
 
 369  8
         if (httpSession == null) {
 370  2
             if (httpSessionExistedAtStartOfRequest) {
 371  0
                 if (logger.isDebugEnabled()) {
 372  0
                     logger.debug("HttpSession is now null, but was not null at start of request; "
 373  
                             + "session was invalidated, so do not create a new session");
 374  
                 }
 375  
             } else {
 376  
                 // Generate a HttpSession only if we need to
 377  
 
 378  2
                 if (!allowSessionCreation) {
 379  0
                     if (logger.isDebugEnabled()) {
 380  0
                         logger.debug("The HttpSession is currently null, and the "
 381  
                                         + "HttpSessionContextIntegrationFilter is prohibited from creating an HttpSession "
 382  
                                         + "(because the allowSessionCreation property is false) - SecurityContext thus not "
 383  
                                         + "stored for next request");
 384  
                     }
 385  2
                 } else if (!contextObject.equals(securityContext)) {
 386  1
                     if (logger.isDebugEnabled()) {
 387  0
                         logger.debug("HttpSession being created as SecurityContextHolder contents are non-default");
 388  
                     }
 389  
 
 390  
                     try {
 391  1
                         httpSession = request.getSession(true);
 392  
                     }
 393  0
                     catch (IllegalStateException ignored) {
 394  1
                     }
 395  
                 } else {
 396  1
                     if (logger.isDebugEnabled()) {
 397  0
                         logger.debug("HttpSession is null, but SecurityContextHolder has not changed from default: ' "
 398  
                                 + securityContext
 399  
                                 + "'; not creating HttpSession or storing SecurityContextHolder contents");
 400  
                     }
 401  
                 }
 402  
             }
 403  
         }
 404  
 
 405  
         // If HttpSession exists, store current SecurityContextHolder contents but only if
 406  
         // the SecurityContext has actually changed (see JIRA SEC-37)
 407  8
         if (httpSession != null && securityContext.hashCode() != contextHashBeforeChainExecution) {
 408  3
             httpSession.setAttribute(ACEGI_SECURITY_CONTEXT_KEY, securityContext);
 409  
 
 410  3
             if (logger.isDebugEnabled()) {
 411  0
                 logger.debug("SecurityContext stored to HttpSession: '" + securityContext + "'");
 412  
             }
 413  
         }
 414  8
     }
 415  
 
 416  
     public SecurityContext generateNewContext() throws ServletException {
 417  
         try {
 418  13
             return (SecurityContext) this.context.newInstance();
 419  
         }
 420  0
         catch (InstantiationException ie) {
 421  0
             throw new ServletException(ie);
 422  
         }
 423  0
         catch (IllegalAccessException iae) {
 424  0
             throw new ServletException(iae);
 425  
         }
 426  
     }
 427  
 
 428  
     /**
 429  
      * Does nothing. We use IoC container lifecycle services instead.
 430  
      *
 431  
      * @param filterConfig ignored
 432  
      * @throws ServletException ignored
 433  
      */
 434  
     public void init(FilterConfig filterConfig) throws ServletException {
 435  9
     }
 436  
 
 437  
     /**
 438  
      * Does nothing. We use IoC container lifecycle services instead.
 439  
      */
 440  
     public void destroy() {
 441  7
     }
 442  
 
 443  
     public boolean isAllowSessionCreation() {
 444  0
         return allowSessionCreation;
 445  
     }
 446  
 
 447  
     public void setAllowSessionCreation(boolean allowSessionCreation) {
 448  2
         this.allowSessionCreation = allowSessionCreation;
 449  2
     }
 450  
 
 451  
     public Class getContext() {
 452  1
         return context;
 453  
     }
 454  
 
 455  
     public void setContext(Class secureContext) {
 456  9
         this.context = secureContext;
 457  9
     }
 458  
 
 459  
     public boolean isForceEagerSessionCreation() {
 460  0
         return forceEagerSessionCreation;
 461  
     }
 462  
 
 463  
     public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
 464  2
         this.forceEagerSessionCreation = forceEagerSessionCreation;
 465  2
     }
 466  
 
 467  
 
 468  
     //~ Inner Classes ==================================================================================================    
 469  
 
 470  
     /**
 471  
      * Wrapper that is applied to every request to update the <code>HttpSession<code> with
 472  
      * the <code>SecurityContext</code> when a <code>sendError()</code> or <code>sendRedirect</code>
 473  
      * happens. See SEC-398. The class contains the fields needed to call
 474  
      * <code>storeSecurityContextInSession()</code>
 475  
      */
 476  
     private class OnRedirectUpdateSessionResponseWrapper extends HttpServletResponseWrapper {
 477  
 
 478  
         HttpServletRequest request;
 479  
         boolean httpSessionExistedAtStartOfRequest;
 480  
         int contextHashBeforeChainExecution;
 481  
 
 482  
         // Used to ensure storeSecurityContextInSession() is only
 483  
         // called once.
 484  8
         boolean sessionUpdateDone = false;
 485  
 
 486  
         /**
 487  
          * Takes the parameters required to call <code>storeSecurityContextInSession()</code> in
 488  
          * addition to the response object we are wrapping.
 489  
          * @see HttpSessionContextIntegrationFilter#storeSecurityContextInSession(SecurityContext, ServletRequest, boolean, int)
 490  
          */
 491  
         public OnRedirectUpdateSessionResponseWrapper(HttpServletResponse response,
 492  
                                                       HttpServletRequest request,
 493  
                                                       boolean httpSessionExistedAtStartOfRequest,
 494  8
                                                       int contextHashBeforeChainExecution) {
 495  8
             super(response);
 496  8
             this.request = request;
 497  8
             this.httpSessionExistedAtStartOfRequest = httpSessionExistedAtStartOfRequest;
 498  8
             this.contextHashBeforeChainExecution = contextHashBeforeChainExecution;
 499  8
         }
 500  
 
 501  
         /**
 502  
          * Makes sure the session is updated before calling the
 503  
          * superclass <code>sendError()</code>
 504  
          */
 505  
         public void sendError(int sc) throws IOException {
 506  0
             doSessionUpdate();
 507  0
             super.sendError(sc);
 508  0
         }
 509  
 
 510  
         /**
 511  
          * Makes sure the session is updated before calling the
 512  
          * superclass <code>sendError()</code>
 513  
          */
 514  
         public void sendError(int sc, String msg) throws IOException {
 515  0
             doSessionUpdate();
 516  0
             super.sendError(sc, msg);
 517  0
         }
 518  
 
 519  
         /**
 520  
          * Makes sure the session is updated before calling the
 521  
          * superclass <code>sendRedirect()</code>
 522  
          */
 523  
         public void sendRedirect(String location) throws IOException {
 524  0
             doSessionUpdate();
 525  0
             super.sendRedirect(location);
 526  0
         }
 527  
 
 528  
         /**
 529  
          * Calls <code>storeSecurityContextInSession()</code>
 530  
          */
 531  
         private void doSessionUpdate() {
 532  0
             if (sessionUpdateDone) {
 533  0
                 return;
 534  
             }
 535  0
             SecurityContext securityContext = SecurityContextHolder.getContext();
 536  0
             storeSecurityContextInSession(securityContext, request,
 537  
                     httpSessionExistedAtStartOfRequest, contextHashBeforeChainExecution);
 538  0
             sessionUpdateDone = true;
 539  0
         }
 540  
 
 541  
         /**
 542  
          * Tells if the response wrapper has called
 543  
          * <code>storeSecurityContextInSession()</code>.
 544  
          */
 545  
         public boolean isSessionUpdateDone() {
 546  8
             return sessionUpdateDone;
 547  
         }
 548  
 
 549  
     }
 550  
 
 551  
 }