SEC-1294: Enable access to beans from ApplicationContext in EL expressions.

ExpressionHandlers are now ApplicationContextAware and set the app context on the SecurityExpressionRoot. A custom PropertyAccessor resolves the properties against the root by looking them up in the app context.
This commit is contained in:
Luke Taylor 2010-03-31 22:37:22 +01:00
parent 12a6ae2ffa
commit 0521d10069
12 changed files with 170 additions and 14 deletions

View File

@ -6,6 +6,7 @@ dependencies {
compile project(':spring-security-core'),
project(':spring-security-web'),
"org.aspectj:aspectjweaver:$aspectjVersion",
'aopalliance:aopalliance:1.0',
"org.springframework:spring-aop:$springVersion",
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-web:$springVersion",
@ -17,10 +18,10 @@ dependencies {
project(':spring-security-openid'),
files(this.project(':spring-security-core').sourceSets.test.classesDir),
'javax.annotation:jsr250-api:1.0',
'aopalliance:aopalliance:1.0',
"org.springframework.ldap:spring-ldap-core:$springLdapVersion",
"org.springframework:spring-jdbc:$springVersion",
"org.springframework:spring-tx:$springVersion"
testRuntime "hsqldb:hsqldb:$hsqlVersion"
testRuntime "hsqldb:hsqldb:$hsqlVersion",
"cglib:cglib-nodep:2.2"
}

View File

@ -74,9 +74,7 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
if (StringUtils.hasText(expressionHandlerRef)) {
logger.info("Using bean '" + expressionHandlerRef + "' as web SecurityExpressionHandler implementation");
} else {
BeanDefinition expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(DefaultWebSecurityExpressionHandler.class).getBeanDefinition();
expressionHandlerRef = pc.getReaderContext().generateBeanName(expressionHandler);
pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef));
expressionHandlerRef = registerDefaultExpressionHandler(pc);
}
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
@ -87,12 +85,19 @@ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinit
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
}
// fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(elt));
return fidsBuilder.getBeanDefinition();
}
static String registerDefaultExpressionHandler(ParserContext pc) {
BeanDefinition expressionHandler = BeanDefinitionBuilder.rootBeanDefinition(DefaultWebSecurityExpressionHandler.class).getBeanDefinition();
String expressionHandlerRef = pc.getReaderContext().generateBeanName(expressionHandler);
pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef));
return expressionHandlerRef;
}
static boolean isUseExpressions(Element elt) {
return "true".equals(elt.getAttribute(ATT_USE_EXPRESSIONS));
}

View File

@ -461,7 +461,12 @@ class HttpConfigurationBuilder {
ManagedList<BeanDefinition> voters = new ManagedList<BeanDefinition>(2);
if (useExpressions) {
voters.add(new RootBeanDefinition(WebExpressionVoter.class));
BeanDefinitionBuilder expressionVoter = BeanDefinitionBuilder.rootBeanDefinition(WebExpressionVoter.class);
RuntimeBeanReference expressionHandler = new RuntimeBeanReference(
FilterInvocationSecurityMetadataSourceParser.registerDefaultExpressionHandler(pc));
expressionVoter.addPropertyValue("expressionHandler", expressionHandler);
voters.add(expressionVoter.getBeanDefinition());
} else {
voters.add(new RootBeanDefinition(RoleVoter.class));
voters.add(new RootBeanDefinition(AuthenticatedVoter.class));

View File

@ -19,6 +19,7 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.annotation.BusinessService;
import org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl;
import org.springframework.security.access.intercept.AfterInvocationProviderManager;
import org.springframework.security.access.intercept.RunAsManagerImpl;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
@ -242,6 +243,20 @@ public class GlobalMethodSecurityBeanDefinitionParserTests {
target.someAdminMethod();
}
@Test
public void beanNameExpressionPropertyIsSupported() {
setContext(
"<global-method-security pre-post-annotations='enabled' proxy-target-class='true'/>" +
"<b:bean id='number' class='java.lang.Integer'>" +
" <b:constructor-arg value='1294'/>" +
"</b:bean>" +
"<b:bean id='target' class='org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl'/>" +
AUTH_PROVIDER_XML);
SecurityContextHolder.getContext().setAuthentication(bob);
ExpressionProtectedBusinessServiceImpl target = (ExpressionProtectedBusinessServiceImpl) appContext.getBean("target");
target.methodWithBeanNamePropertyAccessExpression("x");
}
@Test
public void preAndPostFilterAnnotationsWorkWithLists() {
setContext(

View File

@ -4,6 +4,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.springframework.context.ApplicationContext;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.core.Authentication;
@ -22,6 +23,7 @@ public abstract class SecurityExpressionRoot {
private AuthenticationTrustResolver trustResolver;
private RoleHierarchy roleHierarchy;
private Set<String> roles;
private ApplicationContext applicationContext;
/** Allows "permitAll" expression */
public final boolean permitAll = true;
@ -92,6 +94,14 @@ public abstract class SecurityExpressionRoot {
this.roleHierarchy = roleHierarchy;
}
ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
private Set<String> getAuthoritySet() {
if (roles == null) {
roles = new HashSet<String>();

View File

@ -0,0 +1,38 @@
package org.springframework.security.access.expression;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
@SuppressWarnings("unchecked")
public final class SecurityExpressionRootPropertyAccessor implements PropertyAccessor {
public Class[] CLASSES = {SecurityExpressionRoot.class};
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
ApplicationContext ctx = ((SecurityExpressionRoot)target).getApplicationContext();
if (ctx == null) {
return false;
}
return ctx.containsBean(name);
}
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
return new TypedValue(((SecurityExpressionRoot)target).getApplicationContext().getBean(name));
}
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
return false;
}
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
}
public Class[] getSpecificTargetClasses() {
return CLASSES;
}
}

View File

@ -9,6 +9,9 @@ import java.util.List;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
@ -18,6 +21,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.security.access.PermissionCacheOptimizer;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.SecurityExpressionRootPropertyAccessor;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
@ -31,7 +35,7 @@ import org.springframework.security.core.Authentication;
* @author Luke Taylor
* @since 3.0
*/
public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExpressionHandler {
public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExpressionHandler, ApplicationContextAware {
protected final Log logger = LogFactory.getLog(getClass());
@ -39,8 +43,10 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
private PermissionCacheOptimizer permissionCacheOptimizer = null;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private final SecurityExpressionRootPropertyAccessor sxrpa = new SecurityExpressionRootPropertyAccessor();
private ExpressionParser expressionParser = new SpelExpressionParser();
private RoleHierarchy roleHierarchy;
private ApplicationContext applicationContext;
public DefaultMethodSecurityExpressionHandler() {
}
@ -55,7 +61,9 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
root.setTrustResolver(trustResolver);
root.setPermissionEvaluator(permissionEvaluator);
root.setRoleHierarchy(roleHierarchy);
root.setApplicationContext(applicationContext);
ctx.setRootObject(root);
ctx.addPropertyAccessor(sxrpa);
return ctx;
}
@ -170,4 +178,8 @@ public class DefaultMethodSecurityExpressionHandler implements MethodSecurityExp
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
this.roleHierarchy = roleHierarchy;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

View File

@ -6,6 +6,8 @@ import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@ -23,8 +25,9 @@ class MethodSecurityEvaluationContext extends StandardEvaluationContext {
private static Log logger = LogFactory.getLog(MethodSecurityEvaluationContext.class);
private ParameterNameDiscoverer parameterNameDiscoverer;
private final MethodInvocation mi;
private ApplicationContext appContext;
private boolean argumentsAdded;
private MethodInvocation mi;
/**
* Intended for testing. Don't use in practice as it creates a new parameter resolver
@ -44,6 +47,7 @@ class MethodSecurityEvaluationContext extends StandardEvaluationContext {
@Override
public Object lookupVariable(String name) {
Object variable = super.lookupVariable(name);
if (variable != null) {
return variable;
}
@ -53,7 +57,23 @@ class MethodSecurityEvaluationContext extends StandardEvaluationContext {
argumentsAdded = true;
}
return super.lookupVariable(name);
variable = super.lookupVariable(name);
if (variable != null) {
return variable;
}
if (appContext != null) {
try {
super.setVariable(name, appContext.getBean(name));
return super.lookupVariable(name);
} catch (NoSuchBeanDefinitionException e) {
logger.debug("Bean lookup for variable '" + name + "' failed");
}
}
return null;
}
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {

View File

@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
@ -43,4 +44,9 @@ public class ExpressionProtectedBusinessServiceImpl implements BusinessService {
public Object[] methodReturningAnArray(Object[] someArray) {
return someArray;
}
@PreAuthorize("#x == 'x' and number.intValue() == 1294 ")
public void methodWithBeanNamePropertyAccessExpression(String x) {
}
}

View File

@ -11,7 +11,8 @@
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<sec:http use-expressions="true">
<sec:intercept-url pattern="/**" access="permitAll" />
<!-- Slip in a bean property name EL test -->
<sec:intercept-url pattern="/**" access="fsi.getAccessDecisionManager() eq accessDecisionManager" />
<sec:form-login />
<sec:custom-filter ref="fsi" after="FILTER_SECURITY_INTERCEPTOR " />
</sec:http>

View File

@ -1,10 +1,14 @@
package org.springframework.security.web.access.expression;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.expression.SecurityExpressionRootPropertyAccessor;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
@ -18,22 +22,25 @@ import org.springframework.security.web.FilterInvocation;
* @author Luke Taylor
* @since 3.0
*/
public class DefaultWebSecurityExpressionHandler implements WebSecurityExpressionHandler {
public class DefaultWebSecurityExpressionHandler implements WebSecurityExpressionHandler, ApplicationContextAware {
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private ExpressionParser expressionParser = new SpelExpressionParser();
private final SecurityExpressionRootPropertyAccessor sxrpa = new SecurityExpressionRootPropertyAccessor();
private RoleHierarchy roleHierarchy;
private ApplicationContext applicationContext;
public ExpressionParser getExpressionParser() {
return expressionParser;
}
public EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi) {
StandardEvaluationContext ctx = new StandardEvaluationContext();
SecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi);
root.setTrustResolver(trustResolver);
root.setRoleHierarchy(roleHierarchy);
ctx.setRootObject(root);
root.setApplicationContext(applicationContext);
StandardEvaluationContext ctx = new StandardEvaluationContext(root);
ctx.addPropertyAccessor(sxrpa);
return ctx;
}
@ -41,4 +48,8 @@ public class DefaultWebSecurityExpressionHandler implements WebSecurityExpressio
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
this.roleHierarchy = roleHierarchy;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

View File

@ -0,0 +1,32 @@
package org.springframework.security.web.access.expression;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import org.junit.Test;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
public class DefaultWebSecurityExpressionHandlerTests {
@Test
public void expressionPropertiesAreResolvedAgainsAppContextBeans() throws Exception {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
StaticApplicationContext appContext = new StaticApplicationContext();
RootBeanDefinition bean = new RootBeanDefinition(SecurityConfig.class);
bean.getConstructorArgumentValues().addGenericArgumentValue("ROLE_A");
appContext.registerBeanDefinition("role", bean);
handler.setApplicationContext(appContext);
EvaluationContext ctx = handler.createEvaluationContext(mock(Authentication.class), mock(FilterInvocation.class));
ExpressionParser parser = handler.getExpressionParser();
assertTrue(parser.parseExpression("role.getAttribute() == 'ROLE_A'").getValue(ctx, Boolean.class));
assertTrue(parser.parseExpression("role.attribute == 'ROLE_A'").getValue(ctx, Boolean.class));
}
}