SEC-496: <annotation-driven> element.

This commit is contained in:
Ben Alex 2007-12-04 14:14:17 +00:00
parent 949205b369
commit 9b6c798a52
15 changed files with 293 additions and 50 deletions

View File

@ -20,11 +20,25 @@
<artifactId>spring-security-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-support</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>

View File

@ -36,4 +36,6 @@ public interface BusinessService {
@Secured({"ROLE_USER"})
public void someUserMethod2();
public int someOther(int input);
}

View File

@ -25,4 +25,8 @@ public class BusinessServiceImpl<E extends Entity> implements BusinessService {
public E someUserMethod3(final E entity) {
return entity;
}
public int someOther(int input) {
return input;
}
}

View File

@ -0,0 +1,81 @@
package org.springframework.security.config;
import static org.junit.Assert.fail;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.AuthenticationCredentialsNotFoundException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.annotation.BusinessService;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
/**
* @author Ben Alex
* @version $Id: InterceptMethodsBeanDefinitionDecoratorTests.java 2217 2007-10-27 00:45:30Z luke_t $
*/
public class AnnotatedMethodSecurityBeanDefinitionDecoratorTests {
private static ClassPathXmlApplicationContext appContext;
private BusinessService target;
@BeforeClass
public static void loadContext() {
appContext = new ClassPathXmlApplicationContext("org/springframework/security/config/annotated-method-security.xml");
}
@AfterClass
public static void closeAppContext() {
if (appContext != null) {
appContext.close();
}
}
@Before
public void setUp() {
target = (BusinessService) appContext.getBean("target");
}
@After
public void clearSecurityContext() {
SecurityContextHolder.clearContext();
}
@Test
public void targetShouldPreventProtectedMethodInvocationWithNoContext() {
try {
target.someUserMethod1();
fail("Expected AuthenticationCredentialsNotFoundException");
} catch (AuthenticationCredentialsNotFoundException expected) {
}
}
@Test
public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_USER")});
SecurityContextHolder.getContext().setAuthentication(token);
target.someUserMethod1();
}
@Test
public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_SOMEOTHERROLE")});
SecurityContextHolder.getContext().setAuthentication(token);
try {
target.someAdminMethod();
fail("Expected AccessDeniedException");
} catch (AccessDeniedException expected) {
}
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd">
<b:bean id="target" class="org.springframework.security.annotation.BusinessServiceImpl"/>
<annotation-driven/>
<repository>
<user-service>
<user name="bob" password="bobspassword" authorities="ROLE_A,ROLE_B" />
<user name="bill" password="billspassword" authorities="ROLE_A,ROLE_B,AUTH_OTHER" />
</user-service>
</repository>
</b:beans>

View File

@ -0,0 +1,62 @@
package org.springframework.security.config;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.metadata.Attributes;
import org.springframework.security.intercept.method.MethodDefinitionAttributes;
import org.springframework.security.intercept.method.aopalliance.MethodDefinitionSourceAdvisor;
import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.w3c.dom.Element;
/**
* Processes the top-level "annotation-driven" element.
*
* @author Ben Alex
* @version $Id$
*/
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
public static final String SECURITY_ANNOTATION_ATTRIBUTES_CLASS = "org.springframework.security.annotation.SecurityAnnotationAttributes";
public BeanDefinition parse(Element element, ParserContext parserContext) {
// Reflectively obtain the Annotation-based ObjectDefinitionSource.
// Reflection is used to avoid a compile-time dependency on SECURITY_ANNOTATION_ATTRIBUTES_CLASS, as this parser is in the Java 4 project whereas the dependency is in the Tiger project.
Assert.isTrue(ClassUtils.isPresent(SECURITY_ANNOTATION_ATTRIBUTES_CLASS), "Could not locate class '" + SECURITY_ANNOTATION_ATTRIBUTES_CLASS + "' - please ensure the spring-security-tiger-xxx.jar is in your classpath and you are running Java 5 or above.");
Class clazz = null;
try {
clazz = ClassUtils.forName(SECURITY_ANNOTATION_ATTRIBUTES_CLASS);
} catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
RootBeanDefinition securityAnnotations = new RootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(BeanIds.SECURITY_ANNOTATION_ATTRIBUTES, securityAnnotations);
RootBeanDefinition methodDefinitionAttributes = new RootBeanDefinition(MethodDefinitionAttributes.class);
methodDefinitionAttributes.getPropertyValues().addPropertyValue("attributes", new RuntimeBeanReference(BeanIds.SECURITY_ANNOTATION_ATTRIBUTES));
parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_DEFINITION_ATTRIBUTES, methodDefinitionAttributes);
MethodSecurityInterceptorUtils.registerPostProcessorIfNecessary(parserContext.getRegistry());
RootBeanDefinition interceptor = new RootBeanDefinition(MethodSecurityInterceptor.class);
interceptor.getPropertyValues().addPropertyValue("objectDefinitionSource", new RuntimeBeanReference(BeanIds.METHOD_DEFINITION_ATTRIBUTES));
parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_INTERCEPTOR, interceptor);
RootBeanDefinition advisor = new RootBeanDefinition(MethodDefinitionSourceAdvisor.class);
advisor.getConstructorArgumentValues().addGenericArgumentValue(interceptor);
parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_DEFINITION_SOURCE_ADVISOR, advisor);
RootBeanDefinition daapc = new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class);
parserContext.getRegistry().registerBeanDefinition(BeanIds.DEFAULT_ADVISOR_AUTO_PROXY_CREATOR, daapc);
return null;
}
}

View File

@ -35,5 +35,10 @@ public class BeanIds {
public static final String REMEMBER_ME_SERVICES = "_rememberMeServices";
public static final String DEFAULT_LOGIN_PAGE_GENERATING_FILTER = "_defaultLoginPageFilter";
public static final String SECURITY_CONTEXT_HOLDER_AWARE_REQUEST_FILTER = "_securityContextHolderAwareRequestFilter";
public static final String METHOD_SECURITY_INTERCEPTOR = "_methodSecurityInterceptor";
public static final String METHOD_DEFINITION_SOURCE_ADVISOR = "_methodDefinitionSourceAdvisor";
public static final String SECURITY_ANNOTATION_ATTRIBUTES = "_securityAnnotationAttributes";
public static final String METHOD_DEFINITION_ATTRIBUTES = "_methodDefinitionAttributes";
public static final String DEFAULT_ADVISOR_AUTO_PROXY_CREATOR = "_defaultAdvisorAutoProxyCreator";
}

View File

@ -26,5 +26,6 @@ class Elements {
public static final String ANONYMOUS = "anonymous";
public static final String FILTER_CHAIN = "filter-chain";
public static final String SERVLET_API_INTEGRATION = "servlet-api-integration";
public static final String ANNOTATION_DRIVEN = "annotation-driven";
}

View File

@ -3,16 +3,11 @@ package org.springframework.security.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.config.AbstractInterceptorDrivenBeanDefinitionDecorator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.Ordered;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.ConfigAttributeEditor;
import org.springframework.security.intercept.method.MethodDefinitionMap;
@ -34,36 +29,10 @@ public class InterceptMethodsBeanDefinitionDecorator implements BeanDefinitionDe
private BeanDefinitionDecorator delegate = new InternalInterceptMethodsBeanDefinitionDecorator();
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
registerPostProcessorIfNecessary(parserContext.getRegistry());
MethodSecurityInterceptorUtils.registerPostProcessorIfNecessary(parserContext.getRegistry());
return delegate.decorate(node, definition, parserContext);
}
private void registerPostProcessorIfNecessary(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(BeanIds.INTERCEPT_METHODS_BEAN_FACTORY_POST_PROCESSOR)) {
return;
}
registry.registerBeanDefinition(BeanIds.INTERCEPT_METHODS_BEAN_FACTORY_POST_PROCESSOR,
new RootBeanDefinition(MethodSecurityConfigPostProcessor.class));
}
public static class MethodSecurityConfigPostProcessor implements BeanFactoryPostProcessor, Ordered {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] interceptors = beanFactory.getBeanNamesForType(MethodSecurityInterceptor.class);
for (int i=0; i < interceptors.length; i++) {
BeanDefinition interceptor = beanFactory.getBeanDefinition(interceptors[i]);
ConfigUtils.configureSecurityInterceptor(beanFactory, interceptor);
}
}
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}
}
/**

View File

@ -0,0 +1,53 @@
package org.springframework.security.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.Ordered;
import org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor;
/**
* Provides convenience methods supporting method security configuration.
*
* @author Ben Alex
* @author Luke Taylor
*
*/
abstract class MethodSecurityInterceptorUtils {
private static class MethodSecurityConfigPostProcessor implements BeanFactoryPostProcessor, Ordered {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] interceptors = beanFactory.getBeanNamesForType(MethodSecurityInterceptor.class);
for (int i=0; i < interceptors.length; i++) {
BeanDefinition interceptor = beanFactory.getBeanDefinition(interceptors[i]);
ConfigUtils.configureSecurityInterceptor(beanFactory, interceptor);
}
}
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}
/**
* Causes a BeanFactoryPostProcessor to be registered that will ensure all MethodSecurityInterceptor
* instances are properly configured with an AccessDecisionManager etc.
*
* @param registry to register the BeanPostProcessorWith
*/
public static void registerPostProcessorIfNecessary(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(BeanIds.INTERCEPT_METHODS_BEAN_FACTORY_POST_PROCESSOR)) {
return;
}
registry.registerBeanDefinition(BeanIds.INTERCEPT_METHODS_BEAN_FACTORY_POST_PROCESSOR,
new RootBeanDefinition(MethodSecurityInterceptorUtils.MethodSecurityConfigPostProcessor.class));
}
}

View File

@ -12,11 +12,15 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class SecurityNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser(Elements.LDAP, new LdapBeanDefinitionParser());
// Parsers
registerBeanDefinitionParser(Elements.LDAP, new LdapBeanDefinitionParser());
registerBeanDefinitionParser(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
registerBeanDefinitionParser(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
registerBeanDefinitionParser(Elements.REPOSITORY, new RepositoryBeanDefinitionParser());
registerBeanDefinitionParser(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
registerBeanDefinitionParser(Elements.ANNOTATION_DRIVEN, new AnnotationDrivenBeanDefinitionParser());
// Decorators
registerBeanDefinitionDecorator(Elements.INTERCEPT_METHODS, new InterceptMethodsBeanDefinitionDecorator());
registerBeanDefinitionDecorator(Elements.FILTER_CHAIN_MAP, new FilterChainMapBeanDefinitionDecorator());
}

View File

@ -15,16 +15,17 @@
package org.springframework.security.intercept.method.aopalliance;
import org.springframework.security.intercept.method.MethodDefinitionSource;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.security.intercept.method.MethodDefinitionSource;
/**
* Advisor driven by a {@link MethodDefinitionSource}, used to exclude a {@link MethodSecurityInterceptor} from
@ -39,34 +40,47 @@ import java.lang.reflect.Method;
* @author Ben Alex
* @version $Id$
*/
public class MethodDefinitionSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
public class MethodDefinitionSourceAdvisor extends AbstractPointcutAdvisor {
//~ Instance fields ================================================================================================
private MethodDefinitionSource transactionAttributeSource;
private MethodDefinitionSource attributeSource;
private MethodSecurityInterceptor interceptor;
private Pointcut pointcut;
//~ Constructors ===================================================================================================
public MethodDefinitionSourceAdvisor(MethodSecurityInterceptor advice) {
super(advice);
this.interceptor = advice;
if (advice.getObjectDefinitionSource() == null) {
throw new AopConfigException("Cannot construct a MethodDefinitionSourceAdvisor using a "
+ "MethodSecurityInterceptor that has no ObjectDefinitionSource configured");
}
this.transactionAttributeSource = advice.getObjectDefinitionSource();
this.attributeSource = advice.getObjectDefinitionSource();
this.pointcut = new MethodDefinitionSourcePointcut();
}
//~ Methods ========================================================================================================
public boolean matches(Method m, Class targetClass) {
MethodInvocation methodInvocation = new InternalMethodInvocation(m);
public Pointcut getPointcut() {
return pointcut;
}
return (this.transactionAttributeSource.getAttributes(methodInvocation) != null);
}
public Advice getAdvice() {
return interceptor;
}
//~ Inner Classes ==================================================================================================
class MethodDefinitionSourcePointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method m, Class targetClass) {
MethodInvocation methodInvocation = new InternalMethodInvocation(m);
return attributeSource.getAttributes(methodInvocation) != null;
}
}
/**
* Represents a <code>MethodInvocation</code>.
* <p>Required as <code>MethodDefinitionSource</code> only supports lookup of configuration attributes for

View File

@ -43,6 +43,7 @@ intercept-methods =
intercept-methods.attlist = empty
protect =
## Defines a protected method and the access control configuration attributes that apply to it
element protect {protect.attlist, empty}
@ -55,6 +56,13 @@ protect.attlist &=
attribute access {xsd:string}
annotation-driven =
## Activates security annotation scanning. All beans registered in the Spring application context will be scanned for Spring Security annotations. Where found, the beans will automatically be proxied and security authorization applied to the methods accordingly. Please ensure you have the spring-security-tiger-XXX.jar on your classpath.
element annotation-driven {annotation-driven.attlist}
annotation-driven.attlist = empty
http =
## Container element for HTTP security configuration
element http {http.attlist, (intercept-url+ & form-login? & http-basic? & logout? & concurrent-session-control? & remember-me? & anonymous? & servlet-api-integration?) }

View File

@ -101,6 +101,12 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="annotation-driven">
<xs:annotation>
<xs:documentation>Activates security annotation scanning. All beans registered in the Spring application context will be scanned for Spring Security annotations. Where found, the beans will automatically be proxied and security authorization applied to the methods accordingly. Please ensure you have the spring-security-tiger-XXX.jar on your classpath.</xs:documentation>
</xs:annotation>
<xs:complexType/>
</xs:element>
<xs:element name="http">
<xs:annotation>
<xs:documentation>Container element for HTTP security configuration</xs:documentation>

View File

@ -72,7 +72,7 @@ public class MethodDefinitionSourceAdvisorTests extends TestCase {
Method method = clazz.getMethod("makeLowerCase", new Class[] {String.class});
MethodDefinitionSourceAdvisor advisor = new MethodDefinitionSourceAdvisor(getInterceptor());
assertFalse(advisor.matches(method, clazz));
assertFalse(advisor.getPointcut().getMethodMatcher().matches(method, clazz));
}
public void testAdvisorReturnsTrueWhenMethodInvocationIsDefined()
@ -81,7 +81,7 @@ public class MethodDefinitionSourceAdvisorTests extends TestCase {
Method method = clazz.getMethod("countLength", new Class[] {String.class});
MethodDefinitionSourceAdvisor advisor = new MethodDefinitionSourceAdvisor(getInterceptor());
assertTrue(advisor.matches(method, clazz));
assertTrue(advisor.getPointcut().getMethodMatcher().matches(method, clazz));
}
public void testDetectsImproperlyConfiguredAdvice() {