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.spring.web;
020
021import org.apache.shiro.config.Ini;
022import org.apache.shiro.mgt.SecurityManager;
023import org.apache.shiro.util.CollectionUtils;
024import org.apache.shiro.lang.util.Nameable;
025import org.apache.shiro.lang.util.StringUtils;
026import org.apache.shiro.web.config.IniFilterChainResolverFactory;
027import org.apache.shiro.web.config.ShiroFilterConfiguration;
028import org.apache.shiro.web.filter.AccessControlFilter;
029import org.apache.shiro.web.filter.InvalidRequestFilter;
030import org.apache.shiro.web.filter.authc.AuthenticationFilter;
031import org.apache.shiro.web.filter.authz.AuthorizationFilter;
032import org.apache.shiro.web.filter.mgt.DefaultFilter;
033import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
034import org.apache.shiro.web.filter.mgt.FilterChainManager;
035import org.apache.shiro.web.filter.mgt.FilterChainResolver;
036import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
037import org.apache.shiro.web.mgt.WebSecurityManager;
038import org.apache.shiro.web.servlet.AbstractShiroFilter;
039import org.apache.shiro.web.servlet.OncePerRequestFilter;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042import org.springframework.beans.BeansException;
043import org.springframework.beans.factory.BeanInitializationException;
044import org.springframework.beans.factory.FactoryBean;
045import org.springframework.beans.factory.config.BeanPostProcessor;
046
047import javax.servlet.Filter;
048import java.util.ArrayList;
049import java.util.LinkedHashMap;
050import java.util.List;
051import java.util.Map;
052
053/**
054 * {@link org.springframework.beans.factory.FactoryBean FactoryBean} to be used in Spring-based web applications for
055 * defining the master Shiro Filter.
056 * <h4>Usage</h4>
057 * Declare a DelegatingFilterProxy in {@code web.xml}, matching the filter name to the bean id:
058 * <pre>
059 * &lt;filter&gt;
060 *   &lt;filter-name&gt;<b>shiroFilter</b>&lt;/filter-name&gt;
061 *   &lt;filter-class&gt;org.springframework.web.filter.DelegatingFilterProxy&lt;filter-class&gt;
062 *   &lt;init-param&gt;
063 *    &lt;param-name&gt;targetFilterLifecycle&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 * Then, in your spring XML file that defines your web ApplicationContext:
069 * <pre>
070 * &lt;bean id="<b>shiroFilter</b>" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"&gt;
071 *    &lt;property name="securityManager" ref="securityManager"/&gt;
072 *    &lt;!-- other properties as necessary ... --&gt;
073 * &lt;/bean&gt;
074 * </pre>
075 * <h4>Filter Auto-Discovery</h4>
076 * While there is a {@link #setFilters(java.util.Map) filters} property that allows you to assign a filter beans
077 * to the 'pool' of filters available when defining {@link #setFilterChainDefinitions(String) filter chains}, it is
078 * optional.
079 * <p/>
080 * This implementation is also a {@link BeanPostProcessor} and will acquire
081 * any {@link javax.servlet.Filter Filter} beans defined independently in your Spring application context.  Upon
082 * discovery, they will be automatically added to the {@link #setFilters(java.util.Map) map} keyed by the bean ID.
083 * That ID can then be used in the filter chain definitions, for example:
084 *
085 * <pre>
086 * &lt;bean id="<b>myCustomFilter</b>" class="com.class.that.implements.javax.servlet.Filter"/&gt;
087 * ...
088 * &lt;bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"&gt;
089 *    ...
090 *    &lt;property name="filterChainDefinitions"&gt;
091 *        &lt;value&gt;
092 *            /some/path/** = authc, <b>myCustomFilter</b>
093 *        &lt;/value&gt;
094 *    &lt;/property&gt;
095 * &lt;/bean&gt;
096 * </pre>
097 * <h4>Global Property Values</h4>
098 * Most Shiro servlet Filter implementations exist for defining custom Filter
099 * {@link #setFilterChainDefinitions(String) chain definitions}.  Most implementations subclass one of the
100 * {@link AccessControlFilter}, {@link AuthenticationFilter}, {@link AuthorizationFilter} classes to simplify things,
101 * and each of these 3 classes has configurable properties that are application-specific.
102 * <p/>
103 * A dilemma arises where, if you want to for example set the application's 'loginUrl' for any Filter, you don't want
104 * to have to manually specify that value for <em>each</em> filter instance defined.
105 * <p/>
106 * To prevent configuration duplication, this implementation provides the following properties to allow you
107 * to set relevant values in only one place:
108 * <ul>
109 * <li>{@link #setLoginUrl(String)}</li>
110 * <li>{@link #setSuccessUrl(String)}</li>
111 * <li>{@link #setUnauthorizedUrl(String)}</li>
112 * </ul>
113 * <p>
114 * Then at startup, any values specified via these 3 properties will be applied to all configured
115 * Filter instances so you don't have to specify them individually on each filter instance.  To ensure your own custom
116 * filters benefit from this convenience, your filter implementation should subclass one of the 3 mentioned
117 * earlier.
118 *
119 * @see org.springframework.web.filter.DelegatingFilterProxy DelegatingFilterProxy
120 * @since 1.0
121 */
122public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
123
124    private static final Logger LOGGER = LoggerFactory.getLogger(ShiroFilterFactoryBean.class);
125
126    private SecurityManager securityManager;
127
128    private Map<String, Filter> filters;
129
130    private List<String> globalFilters;
131
132    //urlPathExpression_to_comma-delimited-filter-chain-definition
133    private Map<String, String> filterChainDefinitionMap;
134
135    private String loginUrl;
136    private String successUrl;
137    private String unauthorizedUrl;
138
139    private AbstractShiroFilter instance;
140
141    private ShiroFilterConfiguration filterConfiguration;
142
143    public ShiroFilterFactoryBean() {
144        this.filters = new LinkedHashMap<String, Filter>();
145        this.globalFilters = new ArrayList<>();
146        this.globalFilters.add(DefaultFilter.invalidRequest.name());
147        //order matters!
148        this.filterChainDefinitionMap = new LinkedHashMap<String, String>();
149        this.filterConfiguration = new ShiroFilterConfiguration();
150    }
151
152    /**
153     * Gets the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.  This is a
154     * required property - failure to set it will throw an initialization exception.
155     *
156     * @return the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.
157     */
158    public SecurityManager getSecurityManager() {
159        return securityManager;
160    }
161
162    /**
163     * Sets the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.  This is a
164     * required property - failure to set it will throw an initialization exception.
165     *
166     * @param securityManager the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.
167     */
168    public void setSecurityManager(SecurityManager securityManager) {
169        this.securityManager = securityManager;
170    }
171
172    /**
173     * Gets the application {@code ShiroFilterConfiguration} instance to be used by the constructed Shiro Filter.
174     *
175     * @return the application {@code ShiroFilterConfiguration} instance to be used by the constructed Shiro Filter.
176     */
177    public ShiroFilterConfiguration getShiroFilterConfiguration() {
178        return filterConfiguration;
179    }
180
181    /**
182     * Sets the application {@code ShiroFilterConfiguration} instance to be used by the constructed Shiro Filter.
183     *
184     * @param filterConfiguration the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.
185     */
186    public void setShiroFilterConfiguration(ShiroFilterConfiguration filterConfiguration) {
187        this.filterConfiguration = filterConfiguration;
188    }
189
190    /**
191     * Returns the application's login URL to be assigned to all acquired Filters that subclass
192     * {@link AccessControlFilter} or {@code null} if no value should be assigned globally. The default value
193     * is {@code null}.
194     *
195     * @return the application's login URL to be assigned to all acquired Filters that subclass
196     * {@link AccessControlFilter} or {@code null} if no value should be assigned globally.
197     * @see #setLoginUrl
198     */
199    public String getLoginUrl() {
200        return loginUrl;
201    }
202
203    /**
204     * Sets the application's login URL to be assigned to all acquired Filters that subclass
205     * {@link AccessControlFilter}.  This is a convenience mechanism: for all configured {@link #setFilters filters},
206     * as well for any default ones ({@code authc}, {@code user}, etc.), this value will be passed on to each Filter
207     * via the {@link AccessControlFilter#setLoginUrl(String)} method<b>*</b>.  This eliminates the need to
208     * configure the 'loginUrl' property manually on each filter instance, and instead that can be configured once
209     * via this attribute.
210     * <p/>
211     * <b>*</b>If a filter already has already been explicitly configured with a value, it will
212     * <em>not</em> receive this value. Individual filter configuration overrides this global convenience property.
213     *
214     * @param loginUrl the application's login URL to apply to as a convenience to all discovered
215     *                 {@link AccessControlFilter} instances.
216     * @see AccessControlFilter#setLoginUrl(String)
217     */
218    public void setLoginUrl(String loginUrl) {
219        this.loginUrl = loginUrl;
220    }
221
222    /**
223     * Returns the application's after-login success URL to be assigned to all acquired Filters that subclass
224     * {@link AuthenticationFilter} or {@code null} if no value should be assigned globally. The default value
225     * is {@code null}.
226     *
227     * @return the application's after-login success URL to be assigned to all acquired Filters that subclass
228     * {@link AuthenticationFilter} or {@code null} if no value should be assigned globally.
229     * @see #setSuccessUrl
230     */
231    public String getSuccessUrl() {
232        return successUrl;
233    }
234
235    /**
236     * Sets the application's after-login success URL to be assigned to all acquired Filters that subclass
237     * {@link AuthenticationFilter}.  This is a convenience mechanism: for all configured {@link #setFilters filters},
238     * as well for any default ones ({@code authc}, {@code user}, etc.), this value will be passed on to each Filter
239     * via the {@link AuthenticationFilter#setSuccessUrl(String)} method<b>*</b>.  This eliminates the need to
240     * configure the 'successUrl' property manually on each filter instance, and instead that can be configured once
241     * via this attribute.
242     * <p/>
243     * <b>*</b>If a filter already has already been explicitly configured with a value, it will
244     * <em>not</em> receive this value. Individual filter configuration overrides this global convenience property.
245     *
246     * @param successUrl the application's after-login success URL to apply to as a convenience to all discovered
247     *                   {@link AccessControlFilter} instances.
248     * @see AuthenticationFilter#setSuccessUrl(String)
249     */
250    public void setSuccessUrl(String successUrl) {
251        this.successUrl = successUrl;
252    }
253
254    /**
255     * Returns the application's after-login success URL to be assigned to all acquired Filters that subclass
256     * {@link AuthenticationFilter} or {@code null} if no value should be assigned globally. The default value
257     * is {@code null}.
258     *
259     * @return the application's after-login success URL to be assigned to all acquired Filters that subclass
260     * {@link AuthenticationFilter} or {@code null} if no value should be assigned globally.
261     * @see #setSuccessUrl
262     */
263    public String getUnauthorizedUrl() {
264        return unauthorizedUrl;
265    }
266
267    /**
268     * Sets the application's 'unauthorized' URL to be assigned to all acquired Filters that subclass
269     * {@link AuthorizationFilter}.  This is a convenience mechanism: for all configured {@link #setFilters filters},
270     * as well for any default ones ({@code roles}, {@code perms}, etc.), this value will be passed on to each Filter
271     * via the {@link AuthorizationFilter#setUnauthorizedUrl(String)} method<b>*</b>.  This eliminates the need to
272     * configure the 'unauthorizedUrl' property manually on each filter instance, and instead that can be configured once
273     * via this attribute.
274     * <p/>
275     * <b>*</b>If a filter already has already been explicitly configured with a value, it will
276     * <em>not</em> receive this value. Individual filter configuration overrides this global convenience property.
277     *
278     * @param unauthorizedUrl the application's 'unauthorized' URL to apply to as a convenience to all discovered
279     *                        {@link AuthorizationFilter} instances.
280     * @see AuthorizationFilter#setUnauthorizedUrl(String)
281     */
282    public void setUnauthorizedUrl(String unauthorizedUrl) {
283        this.unauthorizedUrl = unauthorizedUrl;
284    }
285
286    /**
287     * Returns the filterName-to-Filter map of filters available for reference when defining filter chain definitions.
288     * All filter chain definitions will reference filters by the names in this map (i.e. the keys).
289     *
290     * @return the filterName-to-Filter map of filters available for reference when defining filter chain definitions.
291     */
292    public Map<String, Filter> getFilters() {
293        return filters;
294    }
295
296    /**
297     * Sets the filterName-to-Filter map of filters available for reference when creating
298     * {@link #setFilterChainDefinitionMap(java.util.Map) filter chain definitions}.
299     * <p/>
300     * <b>Note:</b> This property is optional:  this {@code FactoryBean} implementation will discover all beans in the
301     * web application context that implement the {@link Filter} interface and automatically add them to this filter
302     * map under their bean name.
303     * <p/>
304     * For example, just defining this bean in a web Spring XML application context:
305     * <pre>
306     * &lt;bean id=&quot;myFilter&quot; class=&quot;com.class.that.implements.javax.servlet.Filter&quot;&gt;
307     * ...
308     * &lt;/bean&gt;</pre>
309     * Will automatically place that bean into this Filters map under the key '<b>myFilter</b>'.
310     *
311     * @param filters the optional filterName-to-Filter map of filters available for reference when creating
312     *                {@link #setFilterChainDefinitionMap (java.util.Map) filter chain definitions}.
313     */
314    public void setFilters(Map<String, Filter> filters) {
315        this.filters = filters;
316    }
317
318    /**
319     * Returns the chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
320     * by the Shiro Filter.  Each map entry should conform to the format defined by the
321     * {@link FilterChainManager#createChain(String, String)} JavaDoc, where the map key is the chain name (e.g. URL
322     * path expression) and the map value is the comma-delimited string chain definition.
323     *
324     * @return he chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
325     * by the Shiro Filter.
326     */
327    public Map<String, String> getFilterChainDefinitionMap() {
328        return filterChainDefinitionMap;
329    }
330
331    /**
332     * Sets the chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
333     * by the Shiro Filter.  Each map entry should conform to the format defined by the
334     * {@link FilterChainManager#createChain(String, String)} JavaDoc, where the map key is the chain name (e.g. URL
335     * path expression) and the map value is the comma-delimited string chain definition.
336     *
337     * @param filterChainDefinitionMap the chainName-to-chainDefinition map of chain definitions to use for creating
338     *                                 filter chains intercepted by the Shiro Filter.
339     */
340    public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) {
341        this.filterChainDefinitionMap = filterChainDefinitionMap;
342    }
343
344    /**
345     * A convenience method that sets the {@link #setFilterChainDefinitionMap(java.util.Map) filterChainDefinitionMap}
346     * property by accepting a {@link java.util.Properties Properties}-compatible string (multi-line key/value pairs).
347     * Each key/value pair must conform to the format defined by the
348     * {@link FilterChainManager#createChain(String, String)} JavaDoc - each property key is an ant URL
349     * path expression and the value is the comma-delimited chain definition.
350     *
351     * @param definitions a {@link java.util.Properties Properties}-compatible string (multi-line key/value pairs)
352     *                    where each key/value pair represents a single urlPathExpression-commaDelimitedChainDefinition.
353     */
354    public void setFilterChainDefinitions(String definitions) {
355        Ini ini = new Ini();
356        ini.load(definitions);
357        //did they explicitly state a 'urls' section?  Not necessary, but just in case:
358        Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
359        if (CollectionUtils.isEmpty(section)) {
360            //no urls section.  Since this _is_ a urls chain definition property, just assume the
361            //default section contains only the definitions:
362            section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
363        }
364        setFilterChainDefinitionMap(section);
365    }
366
367    /**
368     * Sets the list of filters that will be executed against every request.
369     * Defaults to the {@link InvalidRequestFilter} which will block known invalid request attacks.
370     *
371     * @param globalFilters the list of filters to execute before specific path filters.
372     */
373    public void setGlobalFilters(List<String> globalFilters) {
374        this.globalFilters = globalFilters;
375    }
376
377    /**
378     * Lazily creates and returns a {@link AbstractShiroFilter} concrete instance via the
379     * {@link #createInstance} method.
380     *
381     * @return the application's Shiro Filter instance used to filter incoming web requests.
382     * @throws Exception if there is a problem creating the {@code Filter} instance.
383     */
384    public Object getObject() throws Exception {
385        if (instance == null) {
386            instance = createInstance();
387        }
388        return instance;
389    }
390
391    /**
392     * Returns <code>{@link org.apache.shiro.web.servlet.AbstractShiroFilter}.class</code>
393     *
394     * @return <code>{@link org.apache.shiro.web.servlet.AbstractShiroFilter}.class</code>
395     */
396    public Class getObjectType() {
397        return SpringShiroFilter.class;
398    }
399
400    /**
401     * Returns {@code true} always.  There is almost always only ever 1 Shiro {@code Filter} per web application.
402     *
403     * @return {@code true} always.  There is almost always only ever 1 Shiro {@code Filter} per web application.
404     */
405    public boolean isSingleton() {
406        return true;
407    }
408
409    protected FilterChainManager createFilterChainManager() {
410
411        DefaultFilterChainManager manager = new DefaultFilterChainManager();
412        Map<String, Filter> defaultFilters = manager.getFilters();
413        //apply global settings if necessary:
414        for (Filter filter : defaultFilters.values()) {
415            applyGlobalPropertiesIfNecessary(filter);
416        }
417
418        //Apply the acquired and/or configured filters:
419        Map<String, Filter> filters = getFilters();
420        if (!CollectionUtils.isEmpty(filters)) {
421            for (Map.Entry<String, Filter> entry : filters.entrySet()) {
422                String name = entry.getKey();
423                Filter filter = entry.getValue();
424                applyGlobalPropertiesIfNecessary(filter);
425                if (filter instanceof Nameable) {
426                    ((Nameable) filter).setName(name);
427                }
428                //'init' argument is false, since Spring-configured filters should be initialized
429                //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
430                manager.addFilter(name, filter, false);
431            }
432        }
433
434        // set the global filters
435        manager.setGlobalFilters(this.globalFilters);
436
437        //build up the chains:
438        Map<String, String> chains = getFilterChainDefinitionMap();
439        if (!CollectionUtils.isEmpty(chains)) {
440            for (Map.Entry<String, String> entry : chains.entrySet()) {
441                String url = entry.getKey();
442                String chainDefinition = entry.getValue();
443                manager.createChain(url, chainDefinition);
444            }
445        }
446
447        // create the default chain, to match anything the path matching would have missed
448        // TODO this assumes ANT path matching, which might be OK here
449        manager.createDefaultChain("/**");
450
451        return manager;
452    }
453
454    /**
455     * This implementation:
456     * <ol>
457     * <li>Ensures the required {@link #setSecurityManager(org.apache.shiro.mgt.SecurityManager) securityManager}
458     * property has been set</li>
459     * <li>{@link #createFilterChainManager() Creates} a {@link FilterChainManager} instance that reflects the
460     * configured {@link #setFilters(java.util.Map) filters} and
461     * {@link #setFilterChainDefinitionMap(java.util.Map) filter chain definitions}</li>
462     * <li>Wraps the FilterChainManager with a suitable
463     * {@link org.apache.shiro.web.filter.mgt.FilterChainResolver FilterChainResolver} since the Shiro Filter
464     * implementations do not know of {@code FilterChainManager}s</li>
465     * <li>Sets both the {@code SecurityManager} and {@code FilterChainResolver} instances on a new Shiro Filter
466     * instance and returns that filter instance.</li>
467     * </ol>
468     *
469     * @return a new Shiro Filter reflecting any configured filters and filter chain definitions.
470     * @throws Exception if there is a problem creating the AbstractShiroFilter instance.
471     */
472    protected AbstractShiroFilter createInstance() throws Exception {
473
474        LOGGER.debug("Creating Shiro Filter instance.");
475
476        SecurityManager securityManager = getSecurityManager();
477        if (securityManager == null) {
478            String msg = "SecurityManager property must be set.";
479            throw new BeanInitializationException(msg);
480        }
481
482        if (!(securityManager instanceof WebSecurityManager)) {
483            String msg = "The security manager does not implement the WebSecurityManager interface.";
484            throw new BeanInitializationException(msg);
485        }
486
487        FilterChainManager manager = createFilterChainManager();
488
489        //Expose the constructed FilterChainManager by first wrapping it in a
490        // FilterChainResolver implementation. The AbstractShiroFilter implementations
491        // do not know about FilterChainManagers - only resolvers:
492        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
493        chainResolver.setFilterChainManager(manager);
494
495        //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
496        //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
497        //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
498        //injection of the SecurityManager and FilterChainResolver:
499        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver, getShiroFilterConfiguration());
500    }
501
502    private void applyLoginUrlIfNecessary(Filter filter) {
503        String loginUrl = getLoginUrl();
504        if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
505            AccessControlFilter acFilter = (AccessControlFilter) filter;
506            //only apply the login url if they haven't explicitly configured one already:
507            String existingLoginUrl = acFilter.getLoginUrl();
508            if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
509                acFilter.setLoginUrl(loginUrl);
510            }
511        }
512    }
513
514    private void applySuccessUrlIfNecessary(Filter filter) {
515        String successUrl = getSuccessUrl();
516        if (StringUtils.hasText(successUrl) && (filter instanceof AuthenticationFilter)) {
517            AuthenticationFilter authcFilter = (AuthenticationFilter) filter;
518            //only apply the successUrl if they haven't explicitly configured one already:
519            String existingSuccessUrl = authcFilter.getSuccessUrl();
520            if (AuthenticationFilter.DEFAULT_SUCCESS_URL.equals(existingSuccessUrl)) {
521                authcFilter.setSuccessUrl(successUrl);
522            }
523        }
524    }
525
526    private void applyUnauthorizedUrlIfNecessary(Filter filter) {
527        String unauthorizedUrl = getUnauthorizedUrl();
528        if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {
529            AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
530            //only apply the unauthorizedUrl if they haven't explicitly configured one already:
531            String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
532            if (existingUnauthorizedUrl == null) {
533                authzFilter.setUnauthorizedUrl(unauthorizedUrl);
534            }
535        }
536    }
537
538    private void applyGlobalPropertiesIfNecessary(Filter filter) {
539        applyLoginUrlIfNecessary(filter);
540        applySuccessUrlIfNecessary(filter);
541        applyUnauthorizedUrlIfNecessary(filter);
542
543        if (filter instanceof OncePerRequestFilter) {
544            ((OncePerRequestFilter) filter).setFilterOncePerRequest(filterConfiguration.isFilterOncePerRequest());
545        }
546    }
547
548    /**
549     * Inspects a bean, and if it implements the {@link Filter} interface, automatically adds that filter
550     * instance to the internal {@link #setFilters(java.util.Map) filters map} that will be referenced
551     * later during filter chain construction.
552     */
553    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
554        if (bean instanceof Filter) {
555            LOGGER.debug("Found filter chain candidate filter '{}'", beanName);
556            Filter filter = (Filter) bean;
557            applyGlobalPropertiesIfNecessary(filter);
558            getFilters().put(beanName, filter);
559        } else {
560            LOGGER.trace("Ignoring non-Filter bean '{}'", beanName);
561        }
562        return bean;
563    }
564
565    /**
566     * Does nothing - only exists to satisfy the BeanPostProcessor interface and immediately returns the
567     * {@code bean} argument.
568     */
569    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
570        return bean;
571    }
572
573    /**
574     * Ordinarily the {@code AbstractShiroFilter} must be subclassed to additionally perform configuration
575     * and initialization behavior.  Because this {@code FactoryBean} implementation manually builds the
576     * {@link AbstractShiroFilter}'s
577     * {@link AbstractShiroFilter#setSecurityManager(org.apache.shiro.web.mgt.WebSecurityManager) securityManager} and
578     * {@link AbstractShiroFilter#setFilterChainResolver(org.apache.shiro.web.filter.mgt.FilterChainResolver) filterChainResolver}
579     * properties, the only thing left to do is set those properties explicitly.  We do that in a simple
580     * concrete subclass in the constructor.
581     */
582    private static final class SpringShiroFilter extends AbstractShiroFilter {
583
584        protected SpringShiroFilter(WebSecurityManager webSecurityManager,
585                                    FilterChainResolver resolver,
586                                    ShiroFilterConfiguration filterConfiguration) {
587            super();
588            if (webSecurityManager == null) {
589                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
590            }
591            setSecurityManager(webSecurityManager);
592            setShiroFilterConfiguration(filterConfiguration);
593
594            if (resolver != null) {
595                setFilterChainResolver(resolver);
596            }
597        }
598    }
599}