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