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