Coverage Report - org.apache.shiro.web.servlet.AbstractShiroFilter
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractShiroFilter
32%
29/88
18%
6/32
2
AbstractShiroFilter$1
0%
0/4
N/A
2
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.shiro.web.servlet;
 20  
 
 21  
 import org.apache.shiro.SecurityUtils;
 22  
 import org.apache.shiro.session.Session;
 23  
 import org.apache.shiro.subject.ExecutionException;
 24  
 import org.apache.shiro.subject.Subject;
 25  
 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
 26  
 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
 27  
 import org.apache.shiro.web.mgt.WebSecurityManager;
 28  
 import org.apache.shiro.web.subject.WebSubject;
 29  
 import org.slf4j.Logger;
 30  
 import org.slf4j.LoggerFactory;
 31  
 
 32  
 import javax.servlet.FilterChain;
 33  
 import javax.servlet.ServletException;
 34  
 import javax.servlet.ServletRequest;
 35  
 import javax.servlet.ServletResponse;
 36  
 import javax.servlet.http.HttpServletRequest;
 37  
 import javax.servlet.http.HttpServletResponse;
 38  
 import java.io.IOException;
 39  
 import java.util.concurrent.Callable;
 40  
 
 41  
 /**
 42  
  * Abstract base class that provides all standard Shiro request filtering behavior and expects
 43  
  * subclasses to implement configuration-specific logic (INI, XML, .properties, etc).
 44  
  * <p/>
 45  
  * Subclasses should perform configuration and construction logic in an overridden
 46  
  * {@link #init()} method implementation.  That implementation should make available any constructed
 47  
  * {@code SecurityManager} and {@code FilterChainResolver} by calling
 48  
  * {@link #setSecurityManager(org.apache.shiro.web.mgt.WebSecurityManager)} and
 49  
  * {@link #setFilterChainResolver(org.apache.shiro.web.filter.mgt.FilterChainResolver)} methods respectively.
 50  
  * <h3>Static SecurityManager</h3>
 51  
  * By default the {@code SecurityManager} instance enabled by this filter <em>will not</em> be enabled in static
 52  
  * memory via the {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}
 53  
  * method.  Instead, it is expected that Subject instances will always be constructed on a request-processing thread
 54  
  * via instances of this Filter class.
 55  
  * <p/>
 56  
  * However, if you need to construct {@code Subject} instances on separate (non request-processing) threads, it might
 57  
  * be easiest to enable the SecurityManager to be available in static memory via the
 58  
  * {@link SecurityUtils#getSecurityManager()} method.  You can do this by additionally specifying an {@code init-param}:
 59  
  * <pre>
 60  
  * &lt;filter&gt;
 61  
  *     ... other config here ...
 62  
  *     &lt;init-param&gt;
 63  
  *         &lt;param-name&gt;staticSecurityManagerEnabled&lt;/param-name&gt;
 64  
  *         &lt;param-value&gt;true&lt;/param-value&gt;
 65  
  *     &lt;/init-param&gt;
 66  
  * &lt;/filter&gt;
 67  
  * </pre>
 68  
  * See the Shiro <a href="http://shiro.apache.org/subject.html">Subject documentation</a> for more information as to
 69  
  * if you would do this, particularly the sections on the {@code Subject.Builder} and Thread Association.
 70  
  *
 71  
  * @since 1.0
 72  
  * @see <a href="http://shiro.apache.org/subject.html">Subject documentation</a>
 73  
  */
 74  
 public abstract class AbstractShiroFilter extends OncePerRequestFilter {
 75  
 
 76  1
     private static final Logger log = LoggerFactory.getLogger(AbstractShiroFilter.class);
 77  
 
 78  
     private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled";
 79  
 
 80  
     // Reference to the security manager used by this filter
 81  
     private WebSecurityManager securityManager;
 82  
 
 83  
     // Used to determine which chain should handle an incoming request/response
 84  
     private FilterChainResolver filterChainResolver;
 85  
 
 86  
     /**
 87  
      * Whether or not to bind the constructed SecurityManager instance to static memory (via
 88  
      * SecurityUtils.setSecurityManager).  This was added to support https://issues.apache.org/jira/browse/SHIRO-287
 89  
      * @since 1.2
 90  
      */
 91  
     private boolean staticSecurityManagerEnabled;
 92  
 
 93  9
     protected AbstractShiroFilter() {
 94  9
         this.staticSecurityManagerEnabled = false;
 95  9
     }
 96  
 
 97  
     public WebSecurityManager getSecurityManager() {
 98  9
         return securityManager;
 99  
     }
 100  
 
 101  
     public void setSecurityManager(WebSecurityManager sm) {
 102  7
         this.securityManager = sm;
 103  7
     }
 104  
 
 105  
     public FilterChainResolver getFilterChainResolver() {
 106  1
         return filterChainResolver;
 107  
     }
 108  
 
 109  
     public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
 110  4
         this.filterChainResolver = filterChainResolver;
 111  4
     }
 112  
 
 113  
     /**
 114  
      * Returns {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
 115  
      * to static memory (via
 116  
      * {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}),
 117  
      * {@code false} otherwise.
 118  
      * <p/>
 119  
      * The default value is {@code false}.
 120  
      * <p/>
 121  
      *
 122  
      *
 123  
      * @return {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
 124  
      *         to static memory (via {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}),
 125  
      *         {@code false} otherwise.
 126  
      * @since 1.2
 127  
      * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
 128  
      */
 129  
     public boolean isStaticSecurityManagerEnabled() {
 130  7
         return staticSecurityManagerEnabled;
 131  
     }
 132  
 
 133  
     /**
 134  
      * Sets if the constructed {@link #getSecurityManager() securityManager} reference should be bound
 135  
      * to static memory (via {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
 136  
      * <p/>
 137  
      * The default value is {@code false}.
 138  
      *
 139  
      * @param staticSecurityManagerEnabled if the constructed {@link #getSecurityManager() securityManager} reference
 140  
      *                                       should be bound to static memory (via
 141  
      *                                       {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
 142  
      * @since 1.2
 143  
      * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
 144  
      */
 145  
     public void setStaticSecurityManagerEnabled(boolean staticSecurityManagerEnabled) {
 146  1
         this.staticSecurityManagerEnabled = staticSecurityManagerEnabled;
 147  1
     }
 148  
 
 149  
     protected final void onFilterConfigSet() throws Exception {
 150  
         //added in 1.2 for SHIRO-287:
 151  8
         applyStaticSecurityManagerEnabledConfig();
 152  8
         init();
 153  7
         ensureSecurityManager();
 154  
         //added in 1.2 for SHIRO-287:
 155  7
         if (isStaticSecurityManagerEnabled()) {
 156  1
             SecurityUtils.setSecurityManager(getSecurityManager());
 157  
         }
 158  7
     }
 159  
 
 160  
     /**
 161  
      * Checks if the init-param that configures the filter to use static memory has been configured, and if so,
 162  
      * sets the {@link #setStaticSecurityManagerEnabled(boolean)} attribute with the configured value.
 163  
      *
 164  
      * @since 1.2
 165  
      * @see <a href="https://issues.apache.org/jira/browse/SHIRO-287">SHIRO-287</a>
 166  
      */
 167  
     private void applyStaticSecurityManagerEnabledConfig() {
 168  8
         String value = getInitParam(STATIC_INIT_PARAM_NAME);
 169  8
         if (value != null) {
 170  1
             Boolean b = Boolean.valueOf(value);
 171  1
             if (b != null) {
 172  1
                 setStaticSecurityManagerEnabled(b);
 173  
             }
 174  
         }
 175  8
     }
 176  
 
 177  
     public void init() throws Exception {
 178  2
     }
 179  
 
 180  
     /**
 181  
      * A fallback mechanism called in {@link #onFilterConfigSet()} to ensure that the
 182  
      * {@link #getSecurityManager() securityManager} property has been set by configuration, and if not,
 183  
      * creates one automatically.
 184  
      */
 185  
     private void ensureSecurityManager() {
 186  7
         WebSecurityManager securityManager = getSecurityManager();
 187  7
         if (securityManager == null) {
 188  0
             log.info("No SecurityManager configured.  Creating default.");
 189  0
             securityManager = createDefaultSecurityManager();
 190  0
             setSecurityManager(securityManager);
 191  
         }
 192  7
     }
 193  
 
 194  
     protected WebSecurityManager createDefaultSecurityManager() {
 195  0
         return new DefaultWebSecurityManager();
 196  
     }
 197  
 
 198  
     protected boolean isHttpSessions() {
 199  0
         return getSecurityManager().isHttpSessionMode();
 200  
     }
 201  
 
 202  
     /**
 203  
      * Wraps the original HttpServletRequest in a {@link ShiroHttpServletRequest}, which is required for supporting
 204  
      * Servlet Specification behavior backed by a {@link org.apache.shiro.subject.Subject Subject} instance.
 205  
      *
 206  
      * @param orig the original Servlet Container-provided incoming {@code HttpServletRequest} instance.
 207  
      * @return {@link ShiroHttpServletRequest ShiroHttpServletRequest} instance wrapping the original.
 208  
      * @since 1.0
 209  
      */
 210  
     protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
 211  0
         return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions());
 212  
     }
 213  
 
 214  
     /**
 215  
      * Prepares the {@code ServletRequest} instance that will be passed to the {@code FilterChain} for request
 216  
      * processing.
 217  
      * <p/>
 218  
      * If the {@code ServletRequest} is an instance of {@link HttpServletRequest}, the value returned from this method
 219  
      * is obtained by calling {@link #wrapServletRequest(javax.servlet.http.HttpServletRequest)} to allow Shiro-specific
 220  
      * HTTP behavior, otherwise the original {@code ServletRequest} argument is returned.
 221  
      *
 222  
      * @param request  the incoming ServletRequest
 223  
      * @param response the outgoing ServletResponse
 224  
      * @param chain    the Servlet Container provided {@code FilterChain} that will receive the returned request.
 225  
      * @return the {@code ServletRequest} instance that will be passed to the {@code FilterChain} for request processing.
 226  
      * @since 1.0
 227  
      */
 228  
     @SuppressWarnings({"UnusedDeclaration"})
 229  
     protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
 230  0
         ServletRequest toUse = request;
 231  0
         if (request instanceof HttpServletRequest) {
 232  0
             HttpServletRequest http = (HttpServletRequest) request;
 233  0
             toUse = wrapServletRequest(http);
 234  
         }
 235  0
         return toUse;
 236  
     }
 237  
 
 238  
     /**
 239  
      * Returns a new {@link ShiroHttpServletResponse} instance, wrapping the {@code orig} argument, in order to provide
 240  
      * correct URL rewriting behavior required by the Servlet Specification when using Shiro-based sessions (and not
 241  
      * Servlet Container HTTP-based sessions).
 242  
      *
 243  
      * @param orig    the original {@code HttpServletResponse} instance provided by the Servlet Container.
 244  
      * @param request the {@code ShiroHttpServletRequest} instance wrapping the original request.
 245  
      * @return the wrapped ServletResponse instance to use during {@link FilterChain} execution.
 246  
      * @since 1.0
 247  
      */
 248  
     protected ServletResponse wrapServletResponse(HttpServletResponse orig, ShiroHttpServletRequest request) {
 249  0
         return new ShiroHttpServletResponse(orig, getServletContext(), request);
 250  
     }
 251  
 
 252  
     /**
 253  
      * Prepares the {@code ServletResponse} instance that will be passed to the {@code FilterChain} for request
 254  
      * processing.
 255  
      * <p/>
 256  
      * This implementation delegates to {@link #wrapServletRequest(javax.servlet.http.HttpServletRequest)}
 257  
      * only if Shiro-based sessions are enabled (that is, !{@link #isHttpSessions()}) and the request instance is a
 258  
      * {@link ShiroHttpServletRequest}.  This ensures that any URL rewriting that occurs is handled correctly using the
 259  
      * Shiro-managed Session's sessionId and not a servlet container session ID.
 260  
      * <p/>
 261  
      * If HTTP-based sessions are enabled (the default), then this method does nothing and just returns the
 262  
      * {@code ServletResponse} argument as-is, relying on the default Servlet Container URL rewriting logic.
 263  
      *
 264  
      * @param request  the incoming ServletRequest
 265  
      * @param response the outgoing ServletResponse
 266  
      * @param chain    the Servlet Container provided {@code FilterChain} that will receive the returned request.
 267  
      * @return the {@code ServletResponse} instance that will be passed to the {@code FilterChain} during request processing.
 268  
      * @since 1.0
 269  
      */
 270  
     @SuppressWarnings({"UnusedDeclaration"})
 271  
     protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
 272  0
         ServletResponse toUse = response;
 273  0
         if (!isHttpSessions() && (request instanceof ShiroHttpServletRequest) &&
 274  
                 (response instanceof HttpServletResponse)) {
 275  
             //the ShiroHttpServletResponse exists to support URL rewriting for session ids.  This is only needed if
 276  
             //using Shiro sessions (i.e. not simple HttpSession based sessions):
 277  0
             toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);
 278  
         }
 279  0
         return toUse;
 280  
     }
 281  
 
 282  
     /**
 283  
      * Creates a {@link WebSubject} instance to associate with the incoming request/response pair which will be used
 284  
      * throughout the request/response execution.
 285  
      *
 286  
      * @param request  the incoming {@code ServletRequest}
 287  
      * @param response the outgoing {@code ServletResponse}
 288  
      * @return the {@code WebSubject} instance to associate with the request/response execution
 289  
      * @since 1.0
 290  
      */
 291  
     protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
 292  0
         return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
 293  
     }
 294  
 
 295  
     /**
 296  
      * Updates any 'native'  Session's last access time that might exist to the timestamp when this method is called.
 297  
      * If native sessions are not enabled (that is, standard Servlet container sessions are being used) or there is no
 298  
      * session ({@code subject.getSession(false) == null}), this method does nothing.
 299  
      * <p/>This method implementation merely calls
 300  
      * <code>Session.{@link org.apache.shiro.session.Session#touch() touch}()</code> on the session.
 301  
      *
 302  
      * @param request  incoming request - ignored, but available to subclasses that might wish to override this method
 303  
      * @param response outgoing response - ignored, but available to subclasses that might wish to override this method
 304  
      * @since 1.0
 305  
      */
 306  
     @SuppressWarnings({"UnusedDeclaration"})
 307  
     protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {
 308  0
         if (!isHttpSessions()) { //'native' sessions
 309  0
             Subject subject = SecurityUtils.getSubject();
 310  
             //Subject should never _ever_ be null, but just in case:
 311  0
             if (subject != null) {
 312  0
                 Session session = subject.getSession(false);
 313  0
                 if (session != null) {
 314  
                     try {
 315  0
                         session.touch();
 316  0
                     } catch (Throwable t) {
 317  0
                         log.error("session.touch() method invocation has failed.  Unable to update" +
 318  
                                 "the corresponding session's last access time based on the incoming request.", t);
 319  0
                     }
 320  
                 }
 321  
             }
 322  
         }
 323  0
     }
 324  
 
 325  
     /**
 326  
      * {@code doFilterInternal} implementation that sets-up, executes, and cleans-up a Shiro-filtered request.  It
 327  
      * performs the following ordered operations:
 328  
      * <ol>
 329  
      * <li>{@link #prepareServletRequest(ServletRequest, ServletResponse, FilterChain) Prepares}
 330  
      * the incoming {@code ServletRequest} for use during Shiro's processing</li>
 331  
      * <li>{@link #prepareServletResponse(ServletRequest, ServletResponse, FilterChain) Prepares}
 332  
      * the outgoing {@code ServletResponse} for use during Shiro's processing</li>
 333  
      * <li> {@link #createSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) Creates} a
 334  
      * {@link Subject} instance based on the specified request/response pair.</li>
 335  
      * <li>Finally {@link Subject#execute(Runnable) executes} the
 336  
      * {@link #updateSessionLastAccessTime(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} and
 337  
      * {@link #executeChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
 338  
      * methods</li>
 339  
      * </ol>
 340  
      * <p/>
 341  
      * The {@code Subject.}{@link Subject#execute(Runnable) execute(Runnable)} call in step #4 is used as an
 342  
      * implementation technique to guarantee proper thread binding and restoration is completed successfully.
 343  
      *
 344  
      * @param servletRequest  the incoming {@code ServletRequest}
 345  
      * @param servletResponse the outgoing {@code ServletResponse}
 346  
      * @param chain           the container-provided {@code FilterChain} to execute
 347  
      * @throws IOException                    if an IO error occurs
 348  
      * @throws javax.servlet.ServletException if an Throwable other than an IOException
 349  
      */
 350  
     protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
 351  
             throws ServletException, IOException {
 352  
 
 353  0
         Throwable t = null;
 354  
 
 355  
         try {
 356  0
             final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
 357  0
             final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
 358  
 
 359  0
             final Subject subject = createSubject(request, response);
 360  
 
 361  
             //noinspection unchecked
 362  0
             subject.execute(new Callable() {
 363  
                 public Object call() throws Exception {
 364  0
                     updateSessionLastAccessTime(request, response);
 365  0
                     executeChain(request, response, chain);
 366  0
                     return null;
 367  
                 }
 368  
             });
 369  0
         } catch (ExecutionException ex) {
 370  0
             t = ex.getCause();
 371  0
         } catch (Throwable throwable) {
 372  0
             t = throwable;
 373  0
         }
 374  
 
 375  0
         if (t != null) {
 376  0
             if (t instanceof ServletException) {
 377  0
                 throw (ServletException) t;
 378  
             }
 379  0
             if (t instanceof IOException) {
 380  0
                 throw (IOException) t;
 381  
             }
 382  
             //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
 383  0
             String msg = "Filtered request failed.";
 384  0
             throw new ServletException(msg, t);
 385  
         }
 386  0
     }
 387  
 
 388  
     /**
 389  
      * Returns the {@code FilterChain} to execute for the given request.
 390  
      * <p/>
 391  
      * The {@code origChain} argument is the
 392  
      * original {@code FilterChain} supplied by the Servlet Container, but it may be modified to provide
 393  
      * more behavior by pre-pending further chains according to the Shiro configuration.
 394  
      * <p/>
 395  
      * This implementation returns the chain that will actually be executed by acquiring the chain from a
 396  
      * {@link #getFilterChainResolver() filterChainResolver}.  The resolver determines exactly which chain to
 397  
      * execute, typically based on URL configuration.  If no chain is returned from the resolver call
 398  
      * (returns {@code null}), then the {@code origChain} will be returned by default.
 399  
      *
 400  
      * @param request   the incoming ServletRequest
 401  
      * @param response  the outgoing ServletResponse
 402  
      * @param origChain the original {@code FilterChain} provided by the Servlet Container
 403  
      * @return the {@link FilterChain} to execute for the given request
 404  
      * @since 1.0
 405  
      */
 406  
     protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
 407  0
         FilterChain chain = origChain;
 408  
 
 409  0
         FilterChainResolver resolver = getFilterChainResolver();
 410  0
         if (resolver == null) {
 411  0
             log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
 412  0
             return origChain;
 413  
         }
 414  
 
 415  0
         FilterChain resolved = resolver.getChain(request, response, origChain);
 416  0
         if (resolved != null) {
 417  0
             log.trace("Resolved a configured FilterChain for the current request.");
 418  0
             chain = resolved;
 419  
         } else {
 420  0
             log.trace("No FilterChain configured for the current request.  Using the default.");
 421  
         }
 422  
 
 423  0
         return chain;
 424  
     }
 425  
 
 426  
     /**
 427  
      * Executes a {@link FilterChain} for the given request.
 428  
      * <p/>
 429  
      * This implementation first delegates to
 430  
      * <code>{@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain}</code>
 431  
      * to allow the application's Shiro configuration to determine exactly how the chain should execute.  The resulting
 432  
      * value from that call is then executed directly by calling the returned {@code FilterChain}'s
 433  
      * {@link FilterChain#doFilter doFilter} method.  That is:
 434  
      * <pre>
 435  
      * FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
 436  
      * chain.{@link FilterChain#doFilter doFilter}(request,response);</pre>
 437  
      *
 438  
      * @param request   the incoming ServletRequest
 439  
      * @param response  the outgoing ServletResponse
 440  
      * @param origChain the Servlet Container-provided chain that may be wrapped further by an application-configured
 441  
      *                  chain of Filters.
 442  
      * @throws IOException      if the underlying {@code chain.doFilter} call results in an IOException
 443  
      * @throws ServletException if the underlying {@code chain.doFilter} call results in a ServletException
 444  
      * @since 1.0
 445  
      */
 446  
     protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
 447  
             throws IOException, ServletException {
 448  0
         FilterChain chain = getExecutionChain(request, response, origChain);
 449  0
         chain.doFilter(request, response);
 450  0
     }
 451  
 }