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