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 */
019package org.apache.shiro.web.servlet;
020
021import org.apache.shiro.SecurityUtils;
022import org.apache.shiro.session.Session;
023import org.apache.shiro.subject.ExecutionException;
024import org.apache.shiro.subject.Subject;
025import org.apache.shiro.web.filter.mgt.FilterChainResolver;
026import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
027import org.apache.shiro.web.mgt.WebSecurityManager;
028import org.apache.shiro.web.subject.WebSubject;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import javax.servlet.FilterChain;
033import javax.servlet.ServletException;
034import javax.servlet.ServletRequest;
035import javax.servlet.ServletResponse;
036import javax.servlet.http.HttpServletRequest;
037import javax.servlet.http.HttpServletResponse;
038import java.io.IOException;
039import 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 */
074public 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
408        FilterChain chain = origChain;
409
410        FilterChainResolver resolver = getFilterChainResolver();
411        if (resolver == null) {
412            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
413            return origChain;
414        }
415
416        FilterChain resolved = resolver.getChain(request, response, origChain);
417        if (resolved != null) {
418            log.trace("Resolved a configured FilterChain for the current request.");
419            chain = resolved;
420        } else {
421            log.trace("No FilterChain configured for the current request.  Using the default.");
422        }
423
424        return chain;
425    }
426
427    /**
428     * Executes a {@link FilterChain} for the given request.
429     * <p/>
430     * This implementation first delegates to
431     * <code>{@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain}</code>
432     * to allow the application's Shiro configuration to determine exactly how the chain should execute.  The resulting
433     * value from that call is then executed directly by calling the returned {@code FilterChain}'s
434     * {@link FilterChain#doFilter doFilter} method.  That is:
435     * <pre>
436     * FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
437     * chain.{@link FilterChain#doFilter doFilter}(request,response);</pre>
438     *
439     * @param request   the incoming ServletRequest
440     * @param response  the outgoing ServletResponse
441     * @param origChain the Servlet Container-provided chain that may be wrapped further by an application-configured
442     *                  chain of Filters.
443     * @throws IOException      if the underlying {@code chain.doFilter} call results in an IOException
444     * @throws ServletException if the underlying {@code chain.doFilter} call results in a ServletException
445     * @since 1.0
446     */
447    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
448            throws IOException, ServletException {
449        FilterChain chain = getExecutionChain(request, response, origChain);
450        chain.doFilter(request, response);
451    }
452}