View Javadoc
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      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      protected AbstractShiroFilter() {
94          this.staticSecurityManagerEnabled = false;
95      }
96  
97      public WebSecurityManager getSecurityManager() {
98          return securityManager;
99      }
100 
101     public void setSecurityManager(WebSecurityManager sm) {
102         this.securityManager = sm;
103     }
104 
105     public FilterChainResolver getFilterChainResolver() {
106         return filterChainResolver;
107     }
108 
109     public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
110         this.filterChainResolver = filterChainResolver;
111     }
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         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         this.staticSecurityManagerEnabled = staticSecurityManagerEnabled;
147     }
148 
149     protected final void onFilterConfigSet() throws Exception {
150         //added in 1.2 for SHIRO-287:
151         applyStaticSecurityManagerEnabledConfig();
152         init();
153         ensureSecurityManager();
154         //added in 1.2 for SHIRO-287:
155         if (isStaticSecurityManagerEnabled()) {
156             SecurityUtils.setSecurityManager(getSecurityManager());
157         }
158     }
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         String value = getInitParam(STATIC_INIT_PARAM_NAME);
169         if (value != null) {
170             Boolean b = Boolean.valueOf(value);
171             if (b != null) {
172                 setStaticSecurityManagerEnabled(b);
173             }
174         }
175     }
176 
177     public void init() throws Exception {
178     }
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         WebSecurityManager securityManager = getSecurityManager();
187         if (securityManager == null) {
188             log.info("No SecurityManager configured.  Creating default.");
189             securityManager = createDefaultSecurityManager();
190             setSecurityManager(securityManager);
191         }
192     }
193 
194     protected WebSecurityManager createDefaultSecurityManager() {
195         return new DefaultWebSecurityManager();
196     }
197 
198     protected boolean isHttpSessions() {
199         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         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         ServletRequest toUse = request;
231         if (request instanceof HttpServletRequest) {
232             HttpServletRequest http = (HttpServletRequest) request;
233             toUse = wrapServletRequest(http);
234         }
235         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         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         ServletResponse toUse = response;
273         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             toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);
278         }
279         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         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         if (!isHttpSessions()) { //'native' sessions
309             Subject subject = SecurityUtils.getSubject();
310             //Subject should never _ever_ be null, but just in case:
311             if (subject != null) {
312                 Session session = subject.getSession(false);
313                 if (session != null) {
314                     try {
315                         session.touch();
316                     } catch (Throwable t) {
317                         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                     }
320                 }
321             }
322         }
323     }
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         Throwable t = null;
354 
355         try {
356             final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
357             final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
358 
359             final Subject subject = createSubject(request, response);
360 
361             //noinspection unchecked
362             subject.execute(new Callable() {
363                 public Object call() throws Exception {
364                     updateSessionLastAccessTime(request, response);
365                     executeChain(request, response, chain);
366                     return null;
367                 }
368             });
369         } catch (ExecutionException ex) {
370             t = ex.getCause();
371         } catch (Throwable throwable) {
372             t = throwable;
373         }
374 
375         if (t != null) {
376             if (t instanceof ServletException) {
377                 throw (ServletException) t;
378             }
379             if (t instanceof IOException) {
380                 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             String msg = "Filtered request failed.";
384             throw new ServletException(msg, t);
385         }
386     }
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         FilterChain chain = origChain;
408 
409         FilterChainResolver resolver = getFilterChainResolver();
410         if (resolver == null) {
411             log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
412             return origChain;
413         }
414 
415         FilterChain resolved = resolver.getChain(request, response, origChain);
416         if (resolved != null) {
417             log.trace("Resolved a configured FilterChain for the current request.");
418             chain = resolved;
419         } else {
420             log.trace("No FilterChain configured for the current request.  Using the default.");
421         }
422 
423         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         FilterChain chain = getExecutionChain(request, response, origChain);
449         chain.doFilter(request, response);
450     }
451 }