SEC-507: Updated JSR-250 impl to include better support for PermitAll and DenyAll as suggested by Ryan Heaton. Includes JSR-250 voter which is now used by AnnotationDriverbeanDefinitionParser.

This commit is contained in:
Luke Taylor 2008-02-06 13:14:46 +00:00
parent c1895acb6b
commit adbf18a091
9 changed files with 227 additions and 24 deletions

View File

@ -6,21 +6,22 @@ import org.springframework.core.annotation.AnnotationUtils;
import javax.annotation.security.PermitAll; import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed; import javax.annotation.security.RolesAllowed;
import javax.annotation.security.DenyAll;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.ArrayList;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
/** /**
* Java 5 Annotation <code>Attributes</code> metadata implementation used for secure method interception. * Java 5 Annotation {@link Attributes} metadata implementation used for secure method interception using
* the security anotations defined in JSR-250.
* <p> * <p>
* This <code>Attributes</code> implementation will return security configuration for classes described using the * This <code>Attributes</code> implementation will return security configuration for classes described using the
* <code>RolesAllowed</code> Java JEE 5 annotation. * Java JEE 5 annotations (<em>DenyAll</em>, <em>PermitAll</em> and <em>RolesAllowed</em>).
* <p> * <p>
* The <code>SecurityAnnotationAttributes</code> implementation can be used to configure a
* <code>MethodDefinitionAttributes</code> and <code>MethodSecurityInterceptor</code> bean definition.
* *
* @author Mark St.Godard * @author Mark St.Godard
* @author Usama Rashwan * @author Usama Rashwan
@ -31,6 +32,7 @@ import java.lang.annotation.Annotation;
*/ */
public class Jsr250SecurityAnnotationAttributes implements Attributes { public class Jsr250SecurityAnnotationAttributes implements Attributes {
//~ Methods ======================================================================================================== //~ Methods ========================================================================================================
/** /**
@ -48,19 +50,56 @@ public class Jsr250SecurityAnnotationAttributes implements Attributes {
} }
/** /**
* Get the <code>RolesAllowed</code> attributes for a given target method. * Get the attributes for a given target method, acording to JSR-250 precedence rules.
* *
* @param method The target method * @param method The target method
* @return Collection of <code>SecurityConfig</code> * @return Collection of <code>SecurityConfig</code>
* @see Attributes#getAttributes * @see Attributes#getAttributes
*/ */
public Collection<SecurityConfig> getAttributes(Method method) { public Collection<SecurityConfig> getAttributes(Method method) {
Annotation[] annotations = AnnotationUtils.getAnnotations(method); ArrayList<SecurityConfig> attributes = new ArrayList<SecurityConfig>();
Collection<SecurityConfig> attributes = populateSecurityConfigWithRolesAllowed(annotations);
// if there is no RolesAllowed defined on the Method then we will use the one defined on the class if (AnnotationUtils.getAnnotation(method, DenyAll.class) != null) {
// level , according to JSR 250 attributes.add(Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE);
if (attributes.size()==0 && !method.isAnnotationPresent(PermitAll.class)) {
attributes = populateSecurityConfigWithRolesAllowed(method.getDeclaringClass().getDeclaredAnnotations()); return attributes;
}
if (AnnotationUtils.getAnnotation(method, PermitAll.class) != null) {
attributes.add(Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE);
return attributes;
}
RolesAllowed rolesAllowed = AnnotationUtils.getAnnotation(method, RolesAllowed.class);
if (rolesAllowed != null) {
for (String role : rolesAllowed.value()) {
attributes.add(new Jsr250SecurityConfig(role));
}
return attributes;
}
// Now check the class-level attributes:
if (method.getDeclaringClass().getAnnotation(DenyAll.class) != null) {
attributes.add(Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE);
return attributes;
}
if (method.getDeclaringClass().getAnnotation(PermitAll.class) != null) {
attributes.add(Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE);
return attributes;
}
rolesAllowed = method.getDeclaringClass().getAnnotation(RolesAllowed.class);
if (rolesAllowed != null) {
for (String role : rolesAllowed.value()) {
attributes.add(new Jsr250SecurityConfig(role));
}
} }
return attributes; return attributes;

View File

@ -0,0 +1,22 @@
package org.springframework.security.annotation;
import org.springframework.security.SecurityConfig;
import javax.annotation.security.PermitAll;
import javax.annotation.security.DenyAll;
/**
* Security config applicable as a JSR 250 annotation attribute.
*
* @author Ryan Heaton
* @since 2.0
*/
public class Jsr250SecurityConfig extends SecurityConfig {
public static final Jsr250SecurityConfig PERMIT_ALL_ATTRIBUTE = new Jsr250SecurityConfig(PermitAll.class.getName());
public static final Jsr250SecurityConfig DENY_ALL_ATTRIBUTE = new Jsr250SecurityConfig(DenyAll.class.getName());
public Jsr250SecurityConfig(String role) {
super(role);
}
}

View File

@ -0,0 +1,77 @@
package org.springframework.security.annotation;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.Authentication;
import org.springframework.security.vote.AccessDecisionVoter;
import java.util.Iterator;
/**
* Voter on JSR-250 configuration attributes.
*
* @author Ryan Heaton
* @since 2.0
*/
public class Jsr250Voter implements AccessDecisionVoter {
/**
* The specified config attribute is supported if its an instance of a {@link Jsr250SecurityConfig}.
*
* @param configAttribute The config attribute.
* @return whether the config attribute is supported.
*/
public boolean supports(ConfigAttribute configAttribute) {
return configAttribute instanceof Jsr250SecurityConfig;
}
/**
* All classes are supported.
*
* @param clazz the class.
* @return true
*/
public boolean supports(Class clazz) {
return true;
}
/**
* Votes according to JSR 250.
*
* @param authentication The authentication object.
* @param object The access object.
* @param definition The configuration definition.
* @return The vote.
*/
public int vote(Authentication authentication, Object object, ConfigAttributeDefinition definition) {
int result = ACCESS_ABSTAIN;
Iterator iter = definition.getConfigAttributes().iterator();
while (iter.hasNext()) {
ConfigAttribute attribute = (ConfigAttribute) iter.next();
if (Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE.equals(attribute)) {
return ACCESS_GRANTED;
}
if (Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE.equals(attribute)) {
return ACCESS_DENIED;
}
if (supports(attribute)) {
result = ACCESS_DENIED;
// Attempt to find a matching granted authority
for (GrantedAuthority authority : authentication.getAuthorities()) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
}

View File

@ -16,11 +16,14 @@
package org.springframework.security.annotation; package org.springframework.security.annotation;
import javax.annotation.security.RolesAllowed; import javax.annotation.security.RolesAllowed;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
/** /**
* @version $Id$ * @version $Id$
*/ */
@Secured({"ROLE_USER"}) @Secured({"ROLE_USER"})
@PermitAll
public interface BusinessService { public interface BusinessService {
//~ Methods ======================================================================================================== //~ Methods ========================================================================================================

View File

@ -1,12 +1,15 @@
package org.springframework.security.annotation; package org.springframework.security.annotation;
import javax.annotation.security.RolesAllowed; import javax.annotation.security.RolesAllowed;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
/** /**
* *
* @author Luke Taylor * @author Luke Taylor
* @version $Id$ * @version $Id$
*/ */
@PermitAll
public class Jsr250BusinessServiceImpl implements BusinessService { public class Jsr250BusinessServiceImpl implements BusinessService {
@RolesAllowed({"ROLE_USER"}) @RolesAllowed({"ROLE_USER"})
@ -25,7 +28,7 @@ public class Jsr250BusinessServiceImpl implements BusinessService {
public void someAdminMethod() { public void someAdminMethod() {
} }
public int someOther(int input) { public int someOther(int input) {
return input; return input;
} }
} }

View File

@ -10,6 +10,7 @@ import java.util.ArrayList;
import javax.annotation.security.RolesAllowed; import javax.annotation.security.RolesAllowed;
import javax.annotation.security.PermitAll; import javax.annotation.security.PermitAll;
import javax.annotation.security.DenyAll;
/** /**
* @author Luke Taylor * @author Luke Taylor
@ -18,7 +19,8 @@ import javax.annotation.security.PermitAll;
public class Jsr250SecurityAnnotationAttributesTests { public class Jsr250SecurityAnnotationAttributesTests {
Jsr250SecurityAnnotationAttributes attributes = new Jsr250SecurityAnnotationAttributes(); Jsr250SecurityAnnotationAttributes attributes = new Jsr250SecurityAnnotationAttributes();
A a = new A(); A a = new A();
B b = new B(); UserAllowedClass userAllowed = new UserAllowedClass();
DenyAllClass denyAll = new DenyAllClass();
@Test @Test
public void methodWithRolesAllowedHasCorrectAttribute() throws Exception { public void methodWithRolesAllowedHasCorrectAttribute() throws Exception {
@ -31,10 +33,27 @@ public class Jsr250SecurityAnnotationAttributesTests {
} }
@Test @Test
public void permitAllMethodHasNoAttributes() throws Exception { public void permitAllMethodHasPermitAllAttribute() throws Exception {
List<SecurityConfig> accessAttributes = List<SecurityConfig> accessAttributes =
new ArrayList<SecurityConfig>(attributes.getAttributes(a.getClass().getMethod("permitAllMethod"))); new ArrayList<SecurityConfig>(attributes.getAttributes(a.getClass().getMethod("permitAllMethod")));
assertEquals(0, accessAttributes.size()); assertEquals(1, accessAttributes.size());
assertEquals("javax.annotation.security.PermitAll", accessAttributes.get(0).getAttribute());
}
@Test
public void noRoleMethodHasDenyAllAttributeWithDenyAllClass() throws Exception {
List<SecurityConfig> accessAttributes =
new ArrayList<SecurityConfig>(attributes.getAttributes(denyAll.getClass().getMethod("noRoleMethod")));
assertEquals(1, accessAttributes.size());
assertEquals("javax.annotation.security.DenyAll", accessAttributes.get(0).getAttribute());
}
@Test
public void adminMethodHasAdminAttributeWithDenyAllClass() throws Exception {
List<SecurityConfig> accessAttributes =
new ArrayList<SecurityConfig>(attributes.getAttributes(denyAll.getClass().getMethod("adminMethod")));
assertEquals(1, accessAttributes.size());
assertEquals("ADMIN", accessAttributes.get(0).getAttribute());
} }
@Test @Test
@ -45,9 +64,9 @@ public class Jsr250SecurityAnnotationAttributesTests {
} }
@Test @Test
public void classRoleIsAppliedNoRoleMethod() throws Exception { public void classRoleIsAppliedToNoRoleMethod() throws Exception {
List<SecurityConfig> accessAttributes = List<SecurityConfig> accessAttributes =
new ArrayList<SecurityConfig>(attributes.getAttributes(b.getClass().getMethod("noRoleMethod"))); new ArrayList<SecurityConfig>(attributes.getAttributes(userAllowed.getClass().getMethod("noRoleMethod")));
assertEquals(1, accessAttributes.size()); assertEquals(1, accessAttributes.size());
assertEquals("USER", accessAttributes.get(0).getAttribute()); assertEquals("USER", accessAttributes.get(0).getAttribute());
} }
@ -55,7 +74,7 @@ public class Jsr250SecurityAnnotationAttributesTests {
@Test @Test
public void methodRoleOverridesClassRole() throws Exception { public void methodRoleOverridesClassRole() throws Exception {
List<SecurityConfig> accessAttributes = List<SecurityConfig> accessAttributes =
new ArrayList<SecurityConfig>(attributes.getAttributes(b.getClass().getMethod("adminMethod"))); new ArrayList<SecurityConfig>(attributes.getAttributes(userAllowed.getClass().getMethod("adminMethod")));
assertEquals(1, accessAttributes.size()); assertEquals(1, accessAttributes.size());
assertEquals("ADMIN", accessAttributes.get(0).getAttribute()); assertEquals("ADMIN", accessAttributes.get(0).getAttribute());
} }
@ -71,15 +90,25 @@ public class Jsr250SecurityAnnotationAttributesTests {
@PermitAll @PermitAll
public void permitAllMethod() {} public void permitAllMethod() {}
} }
@RolesAllowed("USER") @RolesAllowed("USER")
public static class B { public static class UserAllowedClass {
public void noRoleMethod() {} public void noRoleMethod() {}
@RolesAllowed("ADMIN") @RolesAllowed("ADMIN")
public void adminMethod() {} public void adminMethod() {}
} }
@DenyAll
public static class DenyAllClass {
public void noRoleMethod() {}
@RolesAllowed("ADMIN")
public void adminMethod() {}
}
} }

View File

@ -40,6 +40,15 @@ public class Jsr250AnnotationDrivenBeanDefinitionParserTests {
target.someUserMethod1(); target.someUserMethod1();
} }
@Test
public void permitAllShouldBeDefaultAttribute() {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_USER")});
SecurityContextHolder.getContext().setAuthentication(token);
target.someOther(0);
}
@Test @Test
public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() { public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password", UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password",

View File

@ -25,11 +25,13 @@ import org.w3c.dom.Element;
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
public static final String SECURITY_ANNOTATION_ATTRIBUTES_CLASS = "org.springframework.security.annotation.SecurityAnnotationAttributes"; public static final String SECURITY_ANNOTATION_ATTRIBUTES_CLASS = "org.springframework.security.annotation.SecurityAnnotationAttributes";
public static final String JSR_250_SECURITY_ANNOTATION_ATTRIBUTES_CLASS = "org.springframework.security.annotation.Jsr250SecurityAnnotationAttributes"; public static final String JSR_250_SECURITY_ANNOTATION_ATTRIBUTES_CLASS = "org.springframework.security.annotation.Jsr250SecurityAnnotationAttributes";
public static final String JSR_250_VOTER_CLASS = "org.springframework.security.annotation.Jsr250Voter";
private static final String ATT_ACCESS_MGR = "access-decision-manager-ref"; private static final String ATT_ACCESS_MGR = "access-decision-manager-ref";
private static final String ATT_USE_JSR250 = "jsr250"; private static final String ATT_USE_JSR250 = "jsr250";
public BeanDefinition parse(Element element, ParserContext parserContext) { public BeanDefinition parse(Element element, ParserContext parserContext) {
String className = "true".equals(element.getAttribute(ATT_USE_JSR250)) ? boolean useJsr250 = "true".equals(element.getAttribute(ATT_USE_JSR250));
String className = useJsr250 ?
JSR_250_SECURITY_ANNOTATION_ATTRIBUTES_CLASS : SECURITY_ANNOTATION_ATTRIBUTES_CLASS; JSR_250_SECURITY_ANNOTATION_ATTRIBUTES_CLASS : SECURITY_ANNOTATION_ATTRIBUTES_CLASS;
// Reflectively obtain the Annotation-based ObjectDefinitionSource. // Reflectively obtain the Annotation-based ObjectDefinitionSource.
@ -56,6 +58,11 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
if (!StringUtils.hasText(accessManagerId)) { if (!StringUtils.hasText(accessManagerId)) {
ConfigUtils.registerDefaultAccessManagerIfNecessary(parserContext); ConfigUtils.registerDefaultAccessManagerIfNecessary(parserContext);
if (useJsr250) {
ConfigUtils.addVoter(new RootBeanDefinition(JSR_250_VOTER_CLASS, null, null), parserContext);
}
accessManagerId = BeanIds.ACCESS_MANAGER; accessManagerId = BeanIds.ACCESS_MANAGER;
} }

View File

@ -16,7 +16,6 @@ import org.springframework.security.vote.RoleVoter;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
/** /**
@ -32,15 +31,30 @@ public abstract class ConfigUtils {
static void registerDefaultAccessManagerIfNecessary(ParserContext parserContext) { static void registerDefaultAccessManagerIfNecessary(ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.ACCESS_MANAGER)) { if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.ACCESS_MANAGER)) {
ManagedList defaultVoters = new ManagedList(2);
defaultVoters.add(new RootBeanDefinition(RoleVoter.class));
defaultVoters.add(new RootBeanDefinition(AuthenticatedVoter.class));
BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class); BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class);
accessMgrBuilder.addPropertyValue("decisionVoters", accessMgrBuilder.addPropertyValue("decisionVoters", defaultVoters);
Arrays.asList(new Object[] {new RoleVoter(), new AuthenticatedVoter()}));
BeanDefinition accessMgr = accessMgrBuilder.getBeanDefinition(); BeanDefinition accessMgr = accessMgrBuilder.getBeanDefinition();
parserContext.getRegistry().registerBeanDefinition(BeanIds.ACCESS_MANAGER, accessMgr); parserContext.getRegistry().registerBeanDefinition(BeanIds.ACCESS_MANAGER, accessMgr);
} }
} }
public static void addVoter(BeanDefinition voter, ParserContext parserContext) {
registerDefaultAccessManagerIfNecessary(parserContext);
BeanDefinition accessMgr = parserContext.getRegistry().getBeanDefinition(BeanIds.ACCESS_MANAGER);
ManagedList voters = (ManagedList) accessMgr.getPropertyValues().getPropertyValue("decisionVoters").getValue();
voters.add(voter);
accessMgr.getPropertyValues().addPropertyValue("decisionVoters", voter);
}
/** /**
* Creates and registers the bean definition for the default ProviderManager instance and returns * Creates and registers the bean definition for the default ProviderManager instance and returns
* the BeanDefinition for it. This method will typically be called when registering authentication providers * the BeanDefinition for it. This method will typically be called when registering authentication providers