Spring Security 自定义access decision使用SpEL

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
 

猜你喜欢

转载自aasonwu.iteye.com/blog/2045658