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.authc.AuthenticationFilter;
029import org.apache.shiro.web.filter.authz.AuthorizationFilter;
030import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
031import org.apache.shiro.web.filter.mgt.FilterChainManager;
032import org.apache.shiro.web.filter.mgt.FilterChainResolver;
033import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
034import org.apache.shiro.web.mgt.WebSecurityManager;
035import org.apache.shiro.web.servlet.AbstractShiroFilter;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038import org.springframework.beans.BeansException;
039import org.springframework.beans.factory.BeanInitializationException;
040import org.springframework.beans.factory.FactoryBean;
041import org.springframework.beans.factory.config.BeanPostProcessor;
042
043import javax.servlet.Filter;
044import java.util.LinkedHashMap;
045import 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 * @see org.springframework.web.filter.DelegatingFilterProxy DelegatingFilterProxy
114 * @since 1.0
115 */
116public 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}