001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.shiro.web.servlet;
020    
021    import org.apache.shiro.SecurityUtils;
022    import org.apache.shiro.session.Session;
023    import org.apache.shiro.subject.ExecutionException;
024    import org.apache.shiro.subject.Subject;
025    import org.apache.shiro.web.filter.mgt.FilterChainResolver;
026    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
027    import org.apache.shiro.web.mgt.WebSecurityManager;
028    import org.apache.shiro.web.subject.WebSubject;
029    import org.slf4j.Logger;
030    import org.slf4j.LoggerFactory;
031    
032    import javax.servlet.FilterChain;
033    import javax.servlet.ServletException;
034    import javax.servlet.ServletRequest;
035    import javax.servlet.ServletResponse;
036    import javax.servlet.http.HttpServletRequest;
037    import javax.servlet.http.HttpServletResponse;
038    import java.io.IOException;
039    import java.util.concurrent.Callable;
040    
041    /**
042     * Abstract base class that provides all standard Shiro request filtering behavior and expects
043     * subclasses to implement configuration-specific logic (INI, XML, .properties, etc).
044     * <p/>
045     * Subclasses should perform configuration and construction logic in an overridden
046     * {@link #init()} method implementation.  That implementation should make available any constructed
047     * {@code SecurityManager} and {@code FilterChainResolver} by calling
048     * {@link #setSecurityManager(org.apache.shiro.web.mgt.WebSecurityManager)} and
049     * {@link #setFilterChainResolver(org.apache.shiro.web.filter.mgt.FilterChainResolver)} methods respectively.
050     * <h3>Static SecurityManager</h3>
051     * By default the {@code SecurityManager} instance enabled by this filter <em>will not</em> be enabled in static
052     * memory via the {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}
053     * method.  Instead, it is expected that Subject instances will always be constructed on a request-processing thread
054     * via instances of this Filter class.
055     * <p/>
056     * However, if you need to construct {@code Subject} instances on separate (non request-processing) threads, it might
057     * be easiest to enable the SecurityManager to be available in static memory via the
058     * {@link SecurityUtils#getSecurityManager()} method.  You can do this by additionally specifying an {@code init-param}:
059     * <pre>
060     * &lt;filter&gt;
061     *     ... other config here ...
062     *     &lt;init-param&gt;
063     *         &lt;param-name&gt;staticSecurityManagerEnabled&lt;/param-name&gt;
064     *         &lt;param-value&gt;true&lt;/param-value&gt;
065     *     &lt;/init-param&gt;
066     * &lt;/filter&gt;
067     * </pre>
068     * See the Shiro <a href="http://shiro.apache.org/subject.html">Subject documentation</a> for more information as to
069     * if you would do this, particularly the sections on the {@code Subject.Builder} and Thread Association.
070     *
071     * @since 1.0
072     * @see <a href="http://shiro.apache.org/subject.html">Subject documentation</a>
073     */
074    public abstract class AbstractShiroFilter extends OncePerRequestFilter {
075    
076        private static final Logger log = LoggerFactory.getLogger(AbstractShiroFilter.class);
077    
078        private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled";
079    
080        // Reference to the security manager used by this filter
081        private WebSecurityManager securityManager;
082    
083        // Used to determine which chain should handle an incoming request/response
084        private FilterChainResolver filterChainResolver;
085    
086        /**
087         * Whether or not to bind the constructed SecurityManager instance to static memory (via
088         * SecurityUtils.setSecurityManager).  This was added to support https://issues.apache.org/jira/browse/SHIRO-287
089         * @since 1.2
090         */
091        private boolean staticSecurityManagerEnabled;
092    
093        protected AbstractShiroFilter() {
094            this.staticSecurityManagerEnabled = false;
095        }
096    
097        public WebSecurityManager getSecurityManager() {
098            return securityManager;
099        }
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    }