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