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:
parent
12a6ae2ffa
commit
0521d10069
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue