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 * <filter>
60 * <filter-name><b>shiroFilter</b></filter-name>
61 * <filter-class>org.springframework.web.filter.DelegatingFilterProxy<filter-class>
62 * <init-param>
63 * <param-name>targetFilterLifecycle</param-name>
64 * <param-value>true</param-value>
65 * </init-param>
66 * </filter>
67 * </pre>
68 * Then, in your spring XML file that defines your web ApplicationContext:
69 * <pre>
70 * <bean id="<b>shiroFilter</b>" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
71 * <property name="securityManager" ref="securityManager"/>
72 * <!-- other properties as necessary ... -->
73 * </bean>
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 * <bean id="<b>myCustomFilter</b>" class="com.class.that.implements.javax.servlet.Filter"/>
87 * ...
88 * <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
89 * ...
90 * <property name="filterChainDefinitions">
91 * <value>
92 * /some/path/** = authc, <b>myCustomFilter</b>
93 * </value>
94 * </property>
95 * </bean>
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 * <bean id="myFilter" class="com.class.that.implements.javax.servlet.Filter">
307 * ...
308 * </bean></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 }