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