Spring Security允许通过security命名空间来配置AccessDecisionManager。元素的access-decision-manager-ref属性来指明一个实现了AccessDecisionManager的Spring Bean。Spring Security提供了这个接口的三个实现类,都在org.springframework.security.access.vote包中:
类名 |
描述 |
AffirmativeBased |
如果有任何一个投票器允许访问,请求将被立刻允许,而不管之前可能有的拒绝决定。 |
ConsensusBased |
多数票(允许或拒绝)决定了AccessDecisionManager的结果。平局的投票和空票(全是弃权的)的结果是可配置的。 |
UnanimousBased |
所有的投票器必须全是允许的,否则访问将被拒绝。 |
当Spring Security 上下文使用自动配置的时候,Spring 会自动注册一个 AffirmativeBased 投票管理器,表示只要有一个投票器允许访问,请求将被允许通过。调用过程如下图所示:
观察org.springframework.security.config.http.HttpConfigurationBuilder类的createFilterSecurityInterceptor(BeanReference authManager) 方法代码:
private void createFilterSecurityInterceptor(BeanReference authManager) {
//判断是否配置了use-expressions属性
boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(httpElt);
RootBeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser.createSecurityMetadataSource(interceptUrls, httpElt, pc);
RootBeanDefinition accessDecisionMgr;
ManagedList<BeanDefinition> voters = new ManagedList<BeanDefinition>(2);
//如果use-expressions=true, 则使用WebExpressionVoter, 否则使用RoleVoter和AuthenticatedVoter
if (useExpressions) {
BeanDefinitionBuilder expressionVoter = BeanDefinitionBuilder.rootBeanDefinition(WebExpressionVoter.class);
// Read the expression handler from the FISMS
RuntimeBeanReference expressionHandler = (RuntimeBeanReference)
securityMds.getConstructorArgumentValues().getArgumentValue(1, RuntimeBeanReference.class).getValue();
expressionVoter.addPropertyValue("expressionHandler", expressionHandler);
voters.add(expressionVoter.getBeanDefinition());
} else {
voters.add(new RootBeanDefinition(RoleVoter.class));
voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
}
// 初始化默认的AffirmativeBased
accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);
// 添加投票器
accessDecisionMgr.getConstructorArgumentValues().addGenericArgumentValue(voters);
accessDecisionMgr.setSource(pc.extractSource(httpElt));
// 设置Access Manager
String accessManagerId = httpElt.getAttribute(ATT_ACCESS_MGR);
// 如果配置文件没有明确配置<http>的access-decision-manager-ref属性,
// 默认将上面初始化的AffirmativeBased作为access manager
if (!StringUtils.hasText(accessManagerId)) {
accessManagerId = pc.getReaderContext().generateBeanName(accessDecisionMgr);
pc.registerBeanComponent(new BeanComponentDefinition(accessDecisionMgr, accessManagerId));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class);
// 如果配置文件明确配置<http>的access-decision-manager-ref属性, 则直接添加accessDecisionManager引用该access manager。
builder.addPropertyReference("accessDecisionManager", accessManagerId);
builder.addPropertyValue("authenticationManager", authManager);
if ("false".equals(httpElt.getAttribute(ATT_ONCE_PER_REQUEST))) {
builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
}
builder.addPropertyValue("securityMetadataSource", securityMds);
BeanDefinition fsiBean = builder.getBeanDefinition();
String fsiId = pc.getReaderContext().generateBeanName(fsiBean);
pc.registerBeanComponent(new BeanComponentDefinition(fsiBean,fsiId));
// Create and register a DefaultWebInvocationPrivilegeEvaluator for use with taglibs etc.
BeanDefinition wipe = new RootBeanDefinition(DefaultWebInvocationPrivilegeEvaluator.class);
wipe.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference(fsiId));
pc.registerBeanComponent(new BeanComponentDefinition(wipe, pc.getReaderContext().generateBeanName(wipe)));
this.fsi = new RuntimeBeanReference(fsiId);
}
如果需要实现替换内置的AffirmativeBased管理器,明确定义access manager,例如启用UnanimousBased 并且在<intercept-url>元素中使用SpEL表达式,就必须明确指定使用一个WebExpressionVoter 投票器。见下图:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- 明确指定access manager并使用SpEL表达式 -->
<http auto-config="true" use-expressions="true" access-decision-manager-ref="unanimousBased">
<intercept-url pattern="/css/**" access="permitAll" />
<intercept-url pattern="/fonts/**" access="permitAll" />
<intercept-url pattern="/js/**" access="permitAll" />
<intercept-url pattern="/login.html" access="permitAll" />
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<form-login login-page="/login.html" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user name="admin" authorities="ROLE_ADMIN,ROLE_USER" password="admin" />
<user name="user" authorities="ROLE_USER" password="user" />
</user-service>
</authentication-provider>
</authentication-manager>
<beans:bean id="unanimousBased" class="org.springframework.security.access.vote.UnanimousBased" >
<beans:constructor-arg type="java.util.List">
<beans:list>
<!-- 指定使用WebExpressionVoter -->
<beans:ref bean="webExpressionVoter" />
<beans:ref bean="roleVote" />
<beans:ref bean="authenticatedVote" />
</beans:list>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="webExpressionVoter" class="org.springframework.security.web.access.expression.WebExpressionVoter" />
<beans:bean id="roleVote" class="org.springframework.security.access.vote.RoleVoter" />
<beans:bean id="authenticatedVote" class="org.springframework.security.access.vote.AuthenticatedVoter" />
</beans:beans>
这样配置才能顺利使用SpEL表达式。否则,如果不在投票管理器明确指定WebExpressionVoter,将如下错误 Caused by: java.lang.IllegalArgumentException: Unsupported configuration attributes: [hasRole('ROLE_USER'), permitAll, permitAll, permitAll, permitAll] :
21:09:01.417 [RMI TCP Connection(2)-127.0.0.1] ERROR o.s.web.context.ContextLoader - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChains': Cannot resolve reference to bean 'org.springframework.security.web.DefaultSecurityFilterChain#0' while setting bean property 'sourceList' with key [0]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot resolve reference to bean 'org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0' while setting constructor argument with key [10]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Unsupported configuration attributes: [hasRole('ROLE_USER'), permitAll, permitAll, permitAll, permitAll]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:328) ~[BeanDefinitionValueResolver.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:107) ~[BeanDefinitionValueResolver.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveManagedList(BeanDefinitionValueResolver.java:351) ~[BeanDefinitionValueResolver.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:154) ~[BeanDefinitionValueResolver.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1456) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1197) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304) ~[AbstractBeanFactory$1.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[DefaultSingletonBeanRegistry.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300) ~[AbstractBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195) ~[AbstractBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:681) ~[DefaultListableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760) ~[AbstractApplicationContext.class:4.0.2.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482) ~[AbstractApplicationContext.class:4.0.2.RELEASE]
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403) ~[ContextLoader.class:4.0.2.RELEASE]
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306) ~[ContextLoader.class:4.0.2.RELEASE]
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106) [ContextLoaderListener.class:4.0.2.RELEASE]
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4810) [catalina.jar:8.0.0-RC10]
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5248) [catalina.jar:8.0.0-RC10]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:8.0.0-RC10]
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:726) [catalina.jar:8.0.0-RC10]
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:702) [catalina.jar:8.0.0-RC10]
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:699) [catalina.jar:8.0.0-RC10]
at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1647) [catalina.jar:8.0.0-RC10]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.7.0_51]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[na:1.7.0_51]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.7.0_51]
at java.lang.reflect.Method.invoke(Method.java:606) ~[na:1.7.0_51]
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:300) [tomcat-coyote.jar:8.0.0-RC10]
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819) [na:1.7.0_51]
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801) [na:1.7.0_51]
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:465) [catalina.jar:8.0.0-RC10]
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:415) [catalina.jar:8.0.0-RC10]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.7.0_51]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[na:1.7.0_51]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.7.0_51]
at java.lang.reflect.Method.invoke(Method.java:606) ~[na:1.7.0_51]
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:300) [tomcat-coyote.jar:8.0.0-RC10]
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819) [na:1.7.0_51]
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801) [na:1.7.0_51]
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1487) [na:1.7.0_51]
at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:97) [na:1.7.0_51]
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1328) [na:1.7.0_51]
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1420) [na:1.7.0_51]
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:848) [na:1.7.0_51]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.7.0_51]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[na:1.7.0_51]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.7.0_51]
at java.lang.reflect.Method.invoke(Method.java:606) ~[na:1.7.0_51]
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322) [na:1.7.0_51]
at sun.rmi.transport.Transport$1.run(Transport.java:177) [na:1.7.0_51]
at sun.rmi.transport.Transport$1.run(Transport.java:174) [na:1.7.0_51]
at java.security.AccessController.doPrivileged(Native Method) [na:1.7.0_51]
at sun.rmi.transport.Transport.serviceCall(Transport.java:173) [na:1.7.0_51]
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:556) [na:1.7.0_51]
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:811) [na:1.7.0_51]
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:670) [na:1.7.0_51]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_51]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_51]
at java.lang.Thread.run(Thread.java:744) [na:1.7.0_51]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.DefaultSecurityFilterChain#0': Cannot resolve reference to bean 'org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0' while setting constructor argument with key [10]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Unsupported configuration attributes: [hasRole('ROLE_USER'), permitAll, permitAll, permitAll, permitAll]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:328) ~[BeanDefinitionValueResolver.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:107) ~[BeanDefinitionValueResolver.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveManagedList(BeanDefinitionValueResolver.java:351) ~[BeanDefinitionValueResolver.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:154) ~[BeanDefinitionValueResolver.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:626) ~[ConstructorResolver.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:140) ~[ConstructorResolver.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1114) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1017) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
14-Apr-2014 21:09:01.430 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.apache.catalina.core.StandardContext.startInternal Error listenerStart
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304) ~[AbstractBeanFactory$1.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[DefaultSingletonBeanRegistry.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300) ~[AbstractBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195) ~[AbstractBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:320) ~[BeanDefinitionValueResolver.class:4.0.2.RELEASE]
... 60 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Unsupported configuration attributes: [hasRole('ROLE_USER'), permitAll, permitAll, permitAll, permitAll]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1553) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304) ~[AbstractBeanFactory$1.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[DefaultSingletonBeanRegistry.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300) ~[AbstractBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195) ~[AbstractBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:320) ~[BeanDefinitionValueResolver.class:4.0.2.RELEASE]
... 74 common frames omitted
Caused by: java.lang.IllegalArgumentException: Unsupported configuration attributes: [hasRole('ROLE_USER'), permitAll, permitAll, permitAll, permitAll]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.afterPropertiesSet(AbstractSecurityInterceptor.java:156) ~[AbstractSecurityInterceptor.class:3.2.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549) ~[AbstractAutowireCapableBeanFactory.class:4.0.2.RELEASE]
... 81 common frames omitted