View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.spring.web;
20  
21  import org.apache.shiro.config.Ini;
22  import org.apache.shiro.mgt.SecurityManager;
23  import org.apache.shiro.util.CollectionUtils;
24  import org.apache.shiro.util.Nameable;
25  import org.apache.shiro.util.StringUtils;
26  import org.apache.shiro.web.config.IniFilterChainResolverFactory;
27  import org.apache.shiro.web.filter.AccessControlFilter;
28  import org.apache.shiro.web.filter.authc.AuthenticationFilter;
29  import org.apache.shiro.web.filter.authz.AuthorizationFilter;
30  import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
31  import org.apache.shiro.web.filter.mgt.FilterChainManager;
32  import org.apache.shiro.web.filter.mgt.FilterChainResolver;
33  import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
34  import org.apache.shiro.web.mgt.WebSecurityManager;
35  import org.apache.shiro.web.servlet.AbstractShiroFilter;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  import org.springframework.beans.BeansException;
39  import org.springframework.beans.factory.BeanInitializationException;
40  import org.springframework.beans.factory.FactoryBean;
41  import org.springframework.beans.factory.config.BeanPostProcessor;
42  
43  import javax.servlet.Filter;
44  import java.util.LinkedHashMap;
45  import java.util.Map;
46  
47  /**
48   * {@link org.springframework.beans.factory.FactoryBean FactoryBean} to be used in Spring-based web applications for
49   * defining the master Shiro Filter.
50   * <h4>Usage</h4>
51   * Declare a DelegatingFilterProxy in {@code web.xml}, matching the filter name to the bean id:
52   * <pre>
53   * &lt;filter&gt;
54   *   &lt;filter-name&gt;<b>shiroFilter</b>&lt;/filter-name&gt;
55   *   &lt;filter-class&gt;org.springframework.web.filter.DelegatingFilterProxy&lt;filter-class&gt;
56   *   &lt;init-param&gt;
57   *    &lt;param-name&gt;targetFilterLifecycle&lt;/param-name&gt;
58   *     &lt;param-value&gt;true&lt;/param-value&gt;
59   *   &lt;/init-param&gt;
60   * &lt;/filter&gt;
61   * </pre>
62   * Then, in your spring XML file that defines your web ApplicationContext:
63   * <pre>
64   * &lt;bean id="<b>shiroFilter</b>" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"&gt;
65   *    &lt;property name="securityManager" ref="securityManager"/&gt;
66   *    &lt;!-- other properties as necessary ... --&gt;
67   * &lt;/bean&gt;
68   * </pre>
69   * <h4>Filter Auto-Discovery</h4>
70   * While there is a {@link #setFilters(java.util.Map) filters} property that allows you to assign a filter beans
71   * to the 'pool' of filters available when defining {@link #setFilterChainDefinitions(String) filter chains}, it is
72   * optional.
73   * <p/>
74   * This implementation is also a {@link BeanPostProcessor} and will acquire
75   * any {@link javax.servlet.Filter Filter} beans defined independently in your Spring application context.  Upon
76   * discovery, they will be automatically added to the {@link #setFilters(java.util.Map) map} keyed by the bean ID.
77   * That ID can then be used in the filter chain definitions, for example:
78   *
79   * <pre>
80   * &lt;bean id="<b>myCustomFilter</b>" class="com.class.that.implements.javax.servlet.Filter"/&gt;
81   * ...
82   * &lt;bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"&gt;
83   *    ...
84   *    &lt;property name="filterChainDefinitions"&gt;
85   *        &lt;value&gt;
86   *            /some/path/** = authc, <b>myCustomFilter</b>
87   *        &lt;/value&gt;
88   *    &lt;/property&gt;
89   * &lt;/bean&gt;
90   * </pre>
91   * <h4>Global Property Values</h4>
92   * Most Shiro servlet Filter implementations exist for defining custom Filter
93   * {@link #setFilterChainDefinitions(String) chain definitions}.  Most implementations subclass one of the
94   * {@link AccessControlFilter}, {@link AuthenticationFilter}, {@link AuthorizationFilter} classes to simplify things,
95   * and each of these 3 classes has configurable properties that are application-specific.
96   * <p/>
97   * A dilemma arises where, if you want to for example set the application's 'loginUrl' for any Filter, you don't want
98   * to have to manually specify that value for <em>each</em> filter instance definied.
99   * <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  * @see org.springframework.web.filter.DelegatingFilterProxy DelegatingFilterProxy
114  * @since 1.0
115  */
116 public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
117 
118     private static transient final Logger log = LoggerFactory.getLogger(ShiroFilterFactoryBean.class);
119 
120     private SecurityManager securityManager;
121 
122     private Map<String, Filter> filters;
123 
124     private Map<String, String> filterChainDefinitionMap; //urlPathExpression_to_comma-delimited-filter-chain-definition
125 
126     private String loginUrl;
127     private String successUrl;
128     private String unauthorizedUrl;
129 
130     private AbstractShiroFilter instance;
131 
132     public ShiroFilterFactoryBean() {
133         this.filters = new LinkedHashMap<String, Filter>();
134         this.filterChainDefinitionMap = new LinkedHashMap<String, String>(); //order matters!
135     }
136 
137     /**
138      * Sets the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.  This is a
139      * required property - failure to set it will throw an initialization exception.
140      *
141      * @return the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.
142      */
143     public SecurityManager getSecurityManager() {
144         return securityManager;
145     }
146 
147     /**
148      * Sets the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.  This is a
149      * required property - failure to set it will throw an initialization exception.
150      *
151      * @param securityManager the application {@code SecurityManager} instance to be used by the constructed Shiro Filter.
152      */
153     public void setSecurityManager(SecurityManager securityManager) {
154         this.securityManager = securityManager;
155     }
156 
157     /**
158      * Returns the application's login URL to be assigned to all acquired Filters that subclass
159      * {@link AccessControlFilter} or {@code null} if no value should be assigned globally. The default value
160      * is {@code null}.
161      *
162      * @return the application's login URL to be assigned to all acquired Filters that subclass
163      *         {@link AccessControlFilter} or {@code null} if no value should be assigned globally.
164      * @see #setLoginUrl
165      */
166     public String getLoginUrl() {
167         return loginUrl;
168     }
169 
170     /**
171      * Sets the application's login URL to be assigned to all acquired Filters that subclass
172      * {@link AccessControlFilter}.  This is a convenience mechanism: for all configured {@link #setFilters filters},
173      * as well for any default ones ({@code authc}, {@code user}, etc), this value will be passed on to each Filter
174      * via the {@link AccessControlFilter#setLoginUrl(String)} method<b>*</b>.  This eliminates the need to
175      * configure the 'loginUrl' property manually on each filter instance, and instead that can be configured once
176      * via this attribute.
177      * <p/>
178      * <b>*</b>If a filter already has already been explicitly configured with a value, it will
179      * <em>not</em> receive this value. Individual filter configuration overrides this global convenience property.
180      *
181      * @param loginUrl the application's login URL to apply to as a convenience to all discovered
182      *                 {@link AccessControlFilter} instances.
183      * @see AccessControlFilter#setLoginUrl(String)
184      */
185     public void setLoginUrl(String loginUrl) {
186         this.loginUrl = loginUrl;
187     }
188 
189     /**
190      * Returns the application's after-login success URL to be assigned to all acquired Filters that subclass
191      * {@link AuthenticationFilter} or {@code null} if no value should be assigned globally. The default value
192      * is {@code null}.
193      *
194      * @return the application's after-login success URL to be assigned to all acquired Filters that subclass
195      *         {@link AuthenticationFilter} or {@code null} if no value should be assigned globally.
196      * @see #setSuccessUrl
197      */
198     public String getSuccessUrl() {
199         return successUrl;
200     }
201 
202     /**
203      * Sets the application's after-login success URL to be assigned to all acquired Filters that subclass
204      * {@link AuthenticationFilter}.  This is a convenience mechanism: for all configured {@link #setFilters filters},
205      * as well for any default ones ({@code authc}, {@code user}, etc), this value will be passed on to each Filter
206      * via the {@link AuthenticationFilter#setSuccessUrl(String)} method<b>*</b>.  This eliminates the need to
207      * configure the 'successUrl' property manually on each filter instance, and instead that can be configured once
208      * via this attribute.
209      * <p/>
210      * <b>*</b>If a filter already has already been explicitly configured with a value, it will
211      * <em>not</em> receive this value. Individual filter configuration overrides this global convenience property.
212      *
213      * @param successUrl the application's after-login success URL to apply to as a convenience to all discovered
214      *                   {@link AccessControlFilter} instances.
215      * @see AuthenticationFilter#setSuccessUrl(String)
216      */
217     public void setSuccessUrl(String successUrl) {
218         this.successUrl = successUrl;
219     }
220 
221     /**
222      * Returns the application's after-login success URL to be assigned to all acquired Filters that subclass
223      * {@link AuthenticationFilter} or {@code null} if no value should be assigned globally. The default value
224      * is {@code null}.
225      *
226      * @return the application's after-login success URL to be assigned to all acquired Filters that subclass
227      *         {@link AuthenticationFilter} or {@code null} if no value should be assigned globally.
228      * @see #setSuccessUrl
229      */
230     public String getUnauthorizedUrl() {
231         return unauthorizedUrl;
232     }
233 
234     /**
235      * Sets the application's 'unauthorized' URL to be assigned to all acquired Filters that subclass
236      * {@link AuthorizationFilter}.  This is a convenience mechanism: for all configured {@link #setFilters filters},
237      * as well for any default ones ({@code roles}, {@code perms}, etc), this value will be passed on to each Filter
238      * via the {@link AuthorizationFilter#setUnauthorizedUrl(String)} method<b>*</b>.  This eliminates the need to
239      * configure the 'unauthorizedUrl' property manually on each filter instance, and instead that can be configured once
240      * via this attribute.
241      * <p/>
242      * <b>*</b>If a filter already has already been explicitly configured with a value, it will
243      * <em>not</em> receive this value. Individual filter configuration overrides this global convenience property.
244      *
245      * @param unauthorizedUrl the application's 'unauthorized' URL to apply to as a convenience to all discovered
246      *                        {@link AuthorizationFilter} instances.
247      * @see AuthorizationFilter#setUnauthorizedUrl(String)
248      */
249     public void setUnauthorizedUrl(String unauthorizedUrl) {
250         this.unauthorizedUrl = unauthorizedUrl;
251     }
252 
253     /**
254      * Returns the filterName-to-Filter map of filters available for reference when defining filter chain definitions.
255      * All filter chain definitions will reference filters by the names in this map (i.e. the keys).
256      *
257      * @return the filterName-to-Filter map of filters available for reference when defining filter chain definitions.
258      */
259     public Map<String, Filter> getFilters() {
260         return filters;
261     }
262 
263     /**
264      * Sets the filterName-to-Filter map of filters available for reference when creating
265      * {@link #setFilterChainDefinitionMap(java.util.Map) filter chain definitions}.
266      * <p/>
267      * <b>Note:</b> This property is optional:  this {@code FactoryBean} implementation will discover all beans in the
268      * web application context that implement the {@link Filter} interface and automatically add them to this filter
269      * map under their bean name.
270      * <p/>
271      * For example, just defining this bean in a web Spring XML application context:
272      * <pre>
273      * &lt;bean id=&quot;myFilter&quot; class=&quot;com.class.that.implements.javax.servlet.Filter&quot;&gt;
274      * ...
275      * &lt;/bean&gt;</pre>
276      * Will automatically place that bean into this Filters map under the key '<b>myFilter</b>'.
277      *
278      * @param filters the optional filterName-to-Filter map of filters available for reference when creating
279      *                {@link #setFilterChainDefinitionMap (java.util.Map) filter chain definitions}.
280      */
281     public void setFilters(Map<String, Filter> filters) {
282         this.filters = filters;
283     }
284 
285     /**
286      * Returns the chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
287      * by the Shiro Filter.  Each map entry should conform to the format defined by the
288      * {@link FilterChainManager#createChain(String, String)} JavaDoc, where the map key is the chain name (e.g. URL
289      * path expression) and the map value is the comma-delimited string chain definition.
290      *
291      * @return he chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
292      *         by the Shiro Filter.
293      */
294     public Map<String, String> getFilterChainDefinitionMap() {
295         return filterChainDefinitionMap;
296     }
297 
298     /**
299      * Sets the chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
300      * by the Shiro Filter.  Each map entry should conform to the format defined by the
301      * {@link FilterChainManager#createChain(String, String)} JavaDoc, where the map key is the chain name (e.g. URL
302      * path expression) and the map value is the comma-delimited string chain definition.
303      *
304      * @param filterChainDefinitionMap the chainName-to-chainDefinition map of chain definitions to use for creating
305      *                                 filter chains intercepted by the Shiro Filter.
306      */
307     public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) {
308         this.filterChainDefinitionMap = filterChainDefinitionMap;
309     }
310 
311     /**
312      * A convenience method that sets the {@link #setFilterChainDefinitionMap(java.util.Map) filterChainDefinitionMap}
313      * property by accepting a {@link java.util.Properties Properties}-compatible string (multi-line key/value pairs).
314      * Each key/value pair must conform to the format defined by the
315      * {@link FilterChainManager#createChain(String,String)} JavaDoc - each property key is an ant URL
316      * path expression and the value is the comma-delimited chain definition.
317      *
318      * @param definitions a {@link java.util.Properties Properties}-compatible string (multi-line key/value pairs)
319      *                    where each key/value pair represents a single urlPathExpression-commaDelimitedChainDefinition.
320      */
321     public void setFilterChainDefinitions(String definitions) {
322         Ini ini = new Ini();
323         ini.load(definitions);
324         //did they explicitly state a 'urls' section?  Not necessary, but just in case:
325         Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
326         if (CollectionUtils.isEmpty(section)) {
327             //no urls section.  Since this _is_ a urls chain definition property, just assume the
328             //default section contains only the definitions:
329             section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
330         }
331         setFilterChainDefinitionMap(section);
332     }
333 
334     /**
335      * Lazily creates and returns a {@link AbstractShiroFilter} concrete instance via the
336      * {@link #createInstance} method.
337      *
338      * @return the application's Shiro Filter instance used to filter incoming web requests.
339      * @throws Exception if there is a problem creating the {@code Filter} instance.
340      */
341     public Object getObject() throws Exception {
342         if (instance == null) {
343             instance = createInstance();
344         }
345         return instance;
346     }
347 
348     /**
349      * Returns <code>{@link org.apache.shiro.web.servlet.AbstractShiroFilter}.class</code>
350      *
351      * @return <code>{@link org.apache.shiro.web.servlet.AbstractShiroFilter}.class</code>
352      */
353     public Class getObjectType() {
354         return SpringShiroFilter.class;
355     }
356 
357     /**
358      * Returns {@code true} always.  There is almost always only ever 1 Shiro {@code Filter} per web application.
359      *
360      * @return {@code true} always.  There is almost always only ever 1 Shiro {@code Filter} per web application.
361      */
362     public boolean isSingleton() {
363         return true;
364     }
365 
366     protected FilterChainManager createFilterChainManager() {
367 
368         DefaultFilterChainManager manager = new DefaultFilterChainManager();
369         Map<String, Filter> defaultFilters = manager.getFilters();
370         //apply global settings if necessary:
371         for (Filter filter : defaultFilters.values()) {
372             applyGlobalPropertiesIfNecessary(filter);
373         }
374 
375         //Apply the acquired and/or configured filters:
376         Map<String, Filter> filters = getFilters();
377         if (!CollectionUtils.isEmpty(filters)) {
378             for (Map.Entry<String, Filter> entry : filters.entrySet()) {
379                 String name = entry.getKey();
380                 Filter filter = entry.getValue();
381                 applyGlobalPropertiesIfNecessary(filter);
382                 if (filter instanceof Nameable) {
383                     ((Nameable) filter).setName(name);
384                 }
385                 //'init' argument is false, since Spring-configured filters should be initialized
386                 //in Spring (i.e. 'init-method=blah') or implement InitializingBean:
387                 manager.addFilter(name, filter, false);
388             }
389         }
390 
391         //build up the chains:
392         Map<String, String> chains = getFilterChainDefinitionMap();
393         if (!CollectionUtils.isEmpty(chains)) {
394             for (Map.Entry<String, String> entry : chains.entrySet()) {
395                 String url = entry.getKey();
396                 String chainDefinition = entry.getValue();
397                 manager.createChain(url, chainDefinition);
398             }
399         }
400 
401         return manager;
402     }
403 
404     /**
405      * This implementation:
406      * <ol>
407      * <li>Ensures the required {@link #setSecurityManager(org.apache.shiro.mgt.SecurityManager) securityManager}
408      * property has been set</li>
409      * <li>{@link #createFilterChainManager() Creates} a {@link FilterChainManager} instance that reflects the
410      * configured {@link #setFilters(java.util.Map) filters} and
411      * {@link #setFilterChainDefinitionMap(java.util.Map) filter chain definitions}</li>
412      * <li>Wraps the FilterChainManager with a suitable
413      * {@link org.apache.shiro.web.filter.mgt.FilterChainResolver FilterChainResolver} since the Shiro Filter
414      * implementations do not know of {@code FilterChainManager}s</li>
415      * <li>Sets both the {@code SecurityManager} and {@code FilterChainResolver} instances on a new Shiro Filter
416      * instance and returns that filter instance.</li>
417      * </ol>
418      *
419      * @return a new Shiro Filter reflecting any configured filters and filter chain definitions.
420      * @throws Exception if there is a problem creating the AbstractShiroFilter instance.
421      */
422     protected AbstractShiroFilter createInstance() throws Exception {
423 
424         log.debug("Creating Shiro Filter instance.");
425 
426         SecurityManager securityManager = getSecurityManager();
427         if (securityManager == null) {
428             String msg = "SecurityManager property must be set.";
429             throw new BeanInitializationException(msg);
430         }
431 
432         if (!(securityManager instanceof WebSecurityManager)) {
433             String msg = "The security manager does not implement the WebSecurityManager interface.";
434             throw new BeanInitializationException(msg);
435         }
436 
437         FilterChainManager manager = createFilterChainManager();
438 
439         //Expose the constructed FilterChainManager by first wrapping it in a
440         // FilterChainResolver implementation. The AbstractShiroFilter implementations
441         // do not know about FilterChainManagers - only resolvers:
442         PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
443         chainResolver.setFilterChainManager(manager);
444 
445         //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
446         //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
447         //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
448         //injection of the SecurityManager and FilterChainResolver:
449         return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
450     }
451 
452     private void applyLoginUrlIfNecessary(Filter filter) {
453         String loginUrl = getLoginUrl();
454         if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
455             AccessControlFilter acFilter = (AccessControlFilter) filter;
456             //only apply the login url if they haven't explicitly configured one already:
457             String existingLoginUrl = acFilter.getLoginUrl();
458             if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
459                 acFilter.setLoginUrl(loginUrl);
460             }
461         }
462     }
463 
464     private void applySuccessUrlIfNecessary(Filter filter) {
465         String successUrl = getSuccessUrl();
466         if (StringUtils.hasText(successUrl) && (filter instanceof AuthenticationFilter)) {
467             AuthenticationFilter authcFilter = (AuthenticationFilter) filter;
468             //only apply the successUrl if they haven't explicitly configured one already:
469             String existingSuccessUrl = authcFilter.getSuccessUrl();
470             if (AuthenticationFilter.DEFAULT_SUCCESS_URL.equals(existingSuccessUrl)) {
471                 authcFilter.setSuccessUrl(successUrl);
472             }
473         }
474     }
475 
476     private void applyUnauthorizedUrlIfNecessary(Filter filter) {
477         String unauthorizedUrl = getUnauthorizedUrl();
478         if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {
479             AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
480             //only apply the unauthorizedUrl if they haven't explicitly configured one already:
481             String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
482             if (existingUnauthorizedUrl == null) {
483                 authzFilter.setUnauthorizedUrl(unauthorizedUrl);
484             }
485         }
486     }
487 
488     private void applyGlobalPropertiesIfNecessary(Filter filter) {
489         applyLoginUrlIfNecessary(filter);
490         applySuccessUrlIfNecessary(filter);
491         applyUnauthorizedUrlIfNecessary(filter);
492     }
493 
494     /**
495      * Inspects a bean, and if it implements the {@link Filter} interface, automatically adds that filter
496      * instance to the internal {@link #setFilters(java.util.Map) filters map} that will be referenced
497      * later during filter chain construction.
498      */
499     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
500         if (bean instanceof Filter) {
501             log.debug("Found filter chain candidate filter '{}'", beanName);
502             Filter filter = (Filter) bean;
503             applyGlobalPropertiesIfNecessary(filter);
504             getFilters().put(beanName, filter);
505         } else {
506             log.trace("Ignoring non-Filter bean '{}'", beanName);
507         }
508         return bean;
509     }
510 
511     /**
512      * Does nothing - only exists to satisfy the BeanPostProcessor interface and immediately returns the
513      * {@code bean} argument.
514      */
515     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
516         return bean;
517     }
518 
519     /**
520      * Ordinarily the {@code AbstractShiroFilter} must be subclassed to additionally perform configuration
521      * and initialization behavior.  Because this {@code FactoryBean} implementation manually builds the
522      * {@link AbstractShiroFilter}'s
523      * {@link AbstractShiroFilter#setSecurityManager(org.apache.shiro.web.mgt.WebSecurityManager) securityManager} and
524      * {@link AbstractShiroFilter#setFilterChainResolver(org.apache.shiro.web.filter.mgt.FilterChainResolver) filterChainResolver}
525      * properties, the only thing left to do is set those properties explicitly.  We do that in a simple
526      * concrete subclass in the constructor.
527      */
528     private static final class SpringShiroFilter extends AbstractShiroFilter {
529 
530         protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
531             super();
532             if (webSecurityManager == null) {
533                 throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
534             }
535             setSecurityManager(webSecurityManager);
536             if (resolver != null) {
537                 setFilterChainResolver(resolver);
538             }
539         }
540     }
541 }