SEC-1033: Completed working version of web expression support.

SEC-999: Added getExpressionParser() method to the security handler interface to allow both web and method expression security to obtain a suitable parser from the configuration for parsing their expression attributes.
This commit is contained in:
Luke Taylor 2008-12-08 01:01:14 +00:00
parent fd3990c1f8
commit 6b4045667a
11 changed files with 236 additions and 131 deletions

View File

@ -31,14 +31,6 @@ import org.w3c.dom.Element;
*/
abstract class ConfigUtils {
@SuppressWarnings("unchecked")
static void registerDefaultWebAccessManagerIfNecessary(ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.WEB_ACCESS_MANAGER)) {
parserContext.getRegistry().registerBeanDefinition(BeanIds.WEB_ACCESS_MANAGER,
createAccessManagerBean(RoleVoter.class, AuthenticatedVoter.class));
}
}
@SuppressWarnings("unchecked")
static void registerDefaultMethodAccessManagerIfNecessary(ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(BeanIds.METHOD_ACCESS_MANAGER)) {
@ -48,7 +40,7 @@ abstract class ConfigUtils {
}
@SuppressWarnings("unchecked")
private static BeanDefinition createAccessManagerBean(Class<? extends AccessDecisionVoter>... voters) {
static BeanDefinition createAccessManagerBean(Class<? extends AccessDecisionVoter>... voters) {
ManagedList defaultVoters = new ManagedList(voters.length);
for(Class<? extends AccessDecisionVoter> voter : voters) {

View File

@ -37,17 +37,18 @@ import org.w3c.dom.Element;
* Processes the top-level "global-method-security" element.
*
* @author Ben Alex
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
private final Log logger = LogFactory.getLog(getClass());
static final String SECURED_DEPENDENCY_CLASS = "org.springframework.security.annotation.Secured";
static final String SECURED_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.SecuredMethodDefinitionSource";
static final String EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.expression.method.ExpressionAnnotationMethodDefinitionSource";
static final String JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.Jsr250MethodDefinitionSource";
static final String JSR_250_VOTER_CLASS = "org.springframework.security.annotation.Jsr250Voter";
private static final String SECURED_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.SecuredMethodDefinitionSource";
private static final String EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.expression.method.ExpressionAnnotationMethodDefinitionSource";
private static final String JSR_250_SECURITY_METHOD_DEFINITION_SOURCE_CLASS = "org.springframework.security.annotation.Jsr250MethodDefinitionSource";
private static final String JSR_250_VOTER_CLASS = "org.springframework.security.annotation.Jsr250Voter";
/*
* Internal Bean IDs which are only used within this class
@ -56,7 +57,7 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
static final String INTERCEPTOR_POST_PROCESSOR_ID = "_globalMethodSecurityInterceptorPostProcessor";
static final String ACCESS_MANAGER_ID = "_globalMethodSecurityAccessManager";
private static final String DELEGATING_METHOD_DEFINITION_SOURCE_ID = "_delegatingMethodDefinitionSource";
private static final String EXPRESSION_HANDLER_ID = "_expressionHandler";
private static final String EXPRESSION_HANDLER_ID = "_methodExpressionHandler";
private static final String ATT_ACCESS = "access";
private static final String ATT_EXPRESSION = "expression";
@ -74,9 +75,33 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
boolean jsr250Enabled = "enabled".equals(element.getAttribute(ATT_USE_JSR250));
boolean useSecured = "enabled".equals(element.getAttribute(ATT_USE_SECURED));
boolean expressionsEnabled = "enabled".equals(element.getAttribute(ATT_USE_EXPRESSIONS));
BeanDefinition expressionVoter = null;
if (expressionsEnabled) {
delegates.add(BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS).getBeanDefinition());
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref");
if (StringUtils.hasText(expressionHandlerRef)) {
logger.info("Using bean '" + expressionHandlerRef + "' as method SecurityExpressionHandler implementation");
} else {
parserContext.getRegistry().registerBeanDefinition(EXPRESSION_HANDLER_ID, new RootBeanDefinition(DefaultSecurityExpressionHandler.class));
logger.warn("Expressions were enabled for method security but no SecurityExpressionHandler was configured. " +
"All hasPermision() expressions will evaluate to false.");
expressionHandlerRef = EXPRESSION_HANDLER_ID;
}
BeanDefinitionBuilder expressionVoterBldr = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionVoter.class);
BeanDefinitionBuilder afterInvocationProvider = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionAfterInvocationProvider.class);
expressionVoterBldr.addPropertyReference("expressionHandler", expressionHandlerRef);
expressionVoter = expressionVoterBldr.getBeanDefinition();
// After-invocation provider to handle post-invocation filtering and authorization expression annotations.
afterInvocationProvider.addPropertyReference("expressionHandler", expressionHandlerRef);
ConfigUtils.getRegisteredAfterInvocationProviders(parserContext).add(afterInvocationProvider.getBeanDefinition());
// Add the expression method definition source, which will obtain its parser from the registered expression
// handler
BeanDefinitionBuilder mds = BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_METHOD_DEFINITION_SOURCE_CLASS);
mds.addConstructorArgReference(expressionHandlerRef);
delegates.add(mds.getBeanDefinition());
}
if (useSecured) {
@ -103,7 +128,7 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
String accessManagerId = element.getAttribute(ATT_ACCESS_MGR);
if (!StringUtils.hasText(accessManagerId)) {
registerAccessManager(element, parserContext, jsr250Enabled, expressionsEnabled);
registerAccessManager(parserContext, jsr250Enabled, expressionVoter);
accessManagerId = ACCESS_MANAGER_ID;
}
@ -118,35 +143,17 @@ class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
/**
* Register the default AccessDecisionManager. Adds the special JSR 250 voter jsr-250 is enabled and an
* expression voter if expression-based access control is enabled. If expressions are in use, a after-invocation
* provider will also be registered to handle post-invocation filtering and authorization expression annotations.
* expression voter if expression-based access control is enabled.
*/
@SuppressWarnings("unchecked")
private void registerAccessManager(Element element, ParserContext pc, boolean jsr250Enabled, boolean expressionsEnabled) {
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
private void registerAccessManager(ParserContext pc, boolean jsr250Enabled, BeanDefinition expressionVoter) {
BeanDefinitionBuilder accessMgrBuilder = BeanDefinitionBuilder.rootBeanDefinition(AffirmativeBased.class);
ManagedList voters = new ManagedList(4);
if (expressionsEnabled) {
BeanDefinitionBuilder expressionVoter = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionVoter.class);
BeanDefinitionBuilder afterInvocationProvider = BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionAfterInvocationProvider.class);
String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref");
if (StringUtils.hasText(expressionHandlerRef)) {
logger.info("Using bean '" + expressionHandlerRef + "' as SecurityExpressionHandler implementation");
} else {
pc.getRegistry().registerBeanDefinition(EXPRESSION_HANDLER_ID, new RootBeanDefinition(DefaultSecurityExpressionHandler.class));
logger.warn("Expressions were enabled but no SecurityExpressionHandler was configured. " +
"All hasPermision() expressions will evaluate to false.");
expressionHandlerRef = EXPRESSION_HANDLER_ID;
}
expressionVoter.addPropertyReference("expressionHandler", expressionHandlerRef);
afterInvocationProvider.addPropertyReference("expressionHandler", expressionHandlerRef);
ConfigUtils.getRegisteredAfterInvocationProviders(pc).add(afterInvocationProvider.getBeanDefinition());
voters.add(expressionVoter.getBeanDefinition());
if (expressionVoter != null) {
voters.add(expressionVoter);
}
voters.add(new RootBeanDefinition(RoleVoter.class));
voters.add(new RootBeanDefinition(AuthenticatedVoter.class));

View File

@ -1,5 +1,6 @@
package org.springframework.security.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@ -20,6 +21,7 @@ import org.springframework.security.ConfigAttributeEditor;
import org.springframework.security.SecurityConfig;
import org.springframework.security.context.HttpSessionSecurityContextRepository;
import org.springframework.security.context.SecurityContextPersistenceFilter;
import org.springframework.security.expression.web.WebExpressionVoter;
import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
import org.springframework.security.intercept.web.RequestKey;
@ -37,6 +39,9 @@ import org.springframework.security.util.AntUrlPathMatcher;
import org.springframework.security.util.FilterChainProxy;
import org.springframework.security.util.RegexUrlPathMatcher;
import org.springframework.security.util.UrlMatcher;
import org.springframework.security.vote.AccessDecisionVoter;
import org.springframework.security.vote.AuthenticatedVoter;
import org.springframework.security.vote.RoleVoter;
import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
@ -101,6 +106,10 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
private static final String ATT_SECURITY_CONTEXT_REPOSITORY = "security-context-repository-ref";
private static final String EXPRESSION_FIDS_CLASS = "org.springframework.security.expression.web.ExpressionBasedFilterInvocationDefinitionSource";
private static final String EXPRESSION_HANDLER_CLASS = "org.springframework.security.expression.support.DefaultSecurityExpressionHandler";
private static final String EXPRESSION_HANDLER_ID = "_webExpressionHandler";
@SuppressWarnings("unchecked")
public BeanDefinition parse(Element element, ParserContext parserContext) {
ConfigUtils.registerProviderManagerIfNecessary(parserContext);
@ -125,14 +134,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
registerServletApiFilter(element, parserContext);
// Set up the access manager reference for http
String accessManagerId = element.getAttribute(ATT_ACCESS_MGR);
if (!StringUtils.hasText(accessManagerId)) {
ConfigUtils.registerDefaultWebAccessManagerIfNecessary(parserContext);
accessManagerId = BeanIds.WEB_ACCESS_MANAGER;
}
// Register the portMapper. A default will always be created, even if no element exists.
BeanDefinition portMapper = new PortMappingsBeanDefinitionParser().parse(
DomUtils.getChildElementByTagName(element, Elements.PORT_MAPPINGS), parserContext);
@ -140,7 +141,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
registerExceptionTranslationFilter(element, parserContext, allowSessionCreation);
if (channelRequestMap.size() > 0) {
// At least one channel requirement has been specified
registerChannelProcessingBeans(parserContext, matcher, channelRequestMap);
@ -148,8 +148,47 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
boolean useExpressions = "true".equals(element.getAttribute(ATT_USE_EXPRESSIONS));
registerFilterSecurityInterceptor(element, parserContext, matcher, accessManagerId,
parseInterceptUrlsForFilterInvocationRequestMap(interceptUrlElts, convertPathsToLowerCase, useExpressions, parserContext));
LinkedHashMap<RequestKey, List<ConfigAttribute>> requestToAttributesMap =
parseInterceptUrlsForFilterInvocationRequestMap(interceptUrlElts, convertPathsToLowerCase, useExpressions, parserContext);
BeanDefinitionBuilder fidsBuilder;
Class<? extends AccessDecisionVoter>[] voters;
if (useExpressions) {
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref");
if (StringUtils.hasText(expressionHandlerRef)) {
logger.info("Using bean '" + expressionHandlerRef + "' as web SecurityExpressionHandler implementation");
} else {
parserContext.getRegistry().registerBeanDefinition(EXPRESSION_HANDLER_ID,
BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_HANDLER_CLASS).getBeanDefinition());
expressionHandlerRef = EXPRESSION_HANDLER_ID;
}
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(EXPRESSION_FIDS_CLASS);
fidsBuilder.addConstructorArgValue(matcher);
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
fidsBuilder.addConstructorArgReference(expressionHandlerRef);
voters = new Class[] {WebExpressionVoter.class};
} else {
fidsBuilder = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationDefinitionSource.class);
fidsBuilder.addConstructorArgValue(matcher);
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
voters = new Class[] {RoleVoter.class, AuthenticatedVoter.class};
}
fidsBuilder.addPropertyValue("stripQueryStringFromUrls", matcher instanceof AntUrlPathMatcher);
// Set up the access manager reference for http
String accessManagerId = element.getAttribute(ATT_ACCESS_MGR);
if (!StringUtils.hasText(accessManagerId)) {
parserContext.getRegistry().registerBeanDefinition(BeanIds.WEB_ACCESS_MANAGER,
ConfigUtils.createAccessManagerBean(voters));
accessManagerId = BeanIds.WEB_ACCESS_MANAGER;
}
registerFilterSecurityInterceptor(element, parserContext, accessManagerId, fidsBuilder.getBeanDefinition());
boolean sessionControlEnabled = registerConcurrentSessionControlBeansIfRequired(element, parserContext);
@ -303,8 +342,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.EXCEPTION_TRANSLATION_FILTER));
}
private void registerFilterSecurityInterceptor(Element element, ParserContext pc, UrlMatcher matcher,
String accessManagerId, LinkedHashMap<RequestKey, List<ConfigAttribute>> filterInvocationDefinitionMap) {
private void registerFilterSecurityInterceptor(Element element, ParserContext pc, String accessManagerId,
BeanDefinition fids) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class);
builder.addPropertyReference("accessDecisionManager", accessManagerId);
@ -314,10 +353,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
}
DefaultFilterInvocationDefinitionSource fids =
new DefaultFilterInvocationDefinitionSource(matcher, filterInvocationDefinitionMap);
fids.setStripQueryStringFromUrls(matcher instanceof AntUrlPathMatcher);
builder.addPropertyValue("objectDefinitionSource", fids);
pc.getRegistry().registerBeanDefinition(BeanIds.FILTER_SECURITY_INTERCEPTOR, builder.getBeanDefinition());
ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.FILTER_SECURITY_INTERCEPTOR));
@ -635,7 +670,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
if (useExpressions) {
logger.info("Creating access control expression attribute '" + access + "' for " + key);
attributes = new ArrayList<ConfigAttribute>(1);
// The expression will be parsed later by the ExpressionFilterInvocationDefinitionSource
attributes.add(new SecurityConfig(access));
} else {
attributes = SecurityConfig.createList(StringUtils.commaDelimitedListToStringArray(access));

View File

@ -3,6 +3,7 @@ package org.springframework.security.expression;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.security.Authentication;
import org.springframework.security.intercept.web.FilterInvocation;
@ -15,14 +16,18 @@ import org.springframework.security.intercept.web.FilterInvocation;
* @since 2.5
*/
public interface SecurityExpressionHandler {
/**
* @return an expression parser for the expressions used by the implementation.
*/
ExpressionParser getExpressionParser();
/**
* Provides a evaluation context in which to evaluate security expressions for a method invocation.
* Provides an evaluation context in which to evaluate security expressions for a method invocation.
*/
EvaluationContext createEvaluationContext(Authentication authentication, MethodInvocation mi);
/**
* Provides a evaluation context in which to evaluate security expressions for a web invocation.
* Provides an evaluation context in which to evaluate security expressions for a web invocation.
*/
EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi);

View File

@ -13,6 +13,7 @@ import org.springframework.expression.ParseException;
import org.springframework.expression.spel.SpelExpressionParser;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.config.SecurityConfigurationException;
import org.springframework.security.expression.SecurityExpressionHandler;
import org.springframework.security.expression.annotation.PostAuthorize;
import org.springframework.security.expression.annotation.PostFilter;
import org.springframework.security.expression.annotation.PreAuthorize;
@ -38,7 +39,19 @@ import org.springframework.util.ClassUtils;
* @version $Id$
*/
public class ExpressionAnnotationMethodDefinitionSource extends AbstractMethodDefinitionSource {
private ExpressionParser parser = new SpelExpressionParser();
private ExpressionParser parser;
public ExpressionAnnotationMethodDefinitionSource() {
parser = new SpelExpressionParser();
}
/**
* Constructor which obtains the expression parser from the {@link SecurityExpressionHandler#getExpressionParser() }
* method on the supplied <tt>SecurityExpressionHandler</tt>.
*/
public ExpressionAnnotationMethodDefinitionSource(SecurityExpressionHandler handler) {
parser = handler.getExpressionParser();
}
public List<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
@ -67,7 +80,7 @@ public class ExpressionAnnotationMethodDefinitionSource extends AbstractMethodDe
* for the logic of this method. The ordering here is slightly different in that we consider method-specific
* annotations on an interface before class-level ones.
*/
private <A extends Annotation> A findAnnotation(Method method, Class targetClass, Class<A> annotationClass) {
private <A extends Annotation> A findAnnotation(Method method, Class<?> targetClass, Class<A> annotationClass) {
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);

View File

@ -12,6 +12,8 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelExpressionParser;
import org.springframework.expression.spel.standard.StandardEvaluationContext;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationTrustResolver;
@ -24,7 +26,7 @@ import org.springframework.security.intercept.web.FilterInvocation;
/**
* The standard implementation of <tt>SecurityExpressionHandler</tt>.
* <p>
* A single instance should usually be shared.
* A single instance should usually be shared amongst the beans that require expression support.
*
* @author Luke Taylor
* @version $Id$
@ -37,6 +39,7 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
private ExpressionParser expressionParser = new SpelExpressionParser();
public DefaultSecurityExpressionHandler() {
}
@ -57,6 +60,8 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
public EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi) {
StandardEvaluationContext ctx = new StandardEvaluationContext();
SecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi);
ctx.setRootObject(root);
return ctx;
}
@ -127,6 +132,10 @@ public class DefaultSecurityExpressionHandler implements SecurityExpressionHandl
throw new IllegalArgumentException("Filter target must be a collection or array type, but was " + filterTarget);
}
public ExpressionParser getExpressionParser() {
return expressionParser;
}
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}

View File

@ -1,50 +0,0 @@
package org.springframework.security.expression.support;
import java.io.Serializable;
import org.springframework.security.Authentication;
import org.springframework.security.expression.PermissionEvaluator;
public class MethodInvocationSecurityExpressionRoot extends SecurityExpressionRoot {
private PermissionEvaluator permissionEvaluator;
private Object filterObject;
private Object returnObject;
public final String read = "read";
public final String write = "write";
public final String create = "create";
public final String delete = "delete";
public final String admin = "administration";
MethodInvocationSecurityExpressionRoot(Authentication a) {
super(a);
}
public boolean hasPermission(Object target, Object permission) {
return permissionEvaluator.hasPermission(authentication, target, permission);
}
public boolean hasPermission(Object targetId, String targetType, Object permission) {
return permissionEvaluator.hasPermission(authentication, (Serializable)targetId, targetType, permission);
}
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
public Object getFilterObject() {
return filterObject;
}
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
public Object getReturnObject() {
return returnObject;
}
public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}
}

View File

@ -0,0 +1,19 @@
package org.springframework.security.expression.support;
import org.springframework.security.Authentication;
import org.springframework.security.intercept.web.FilterInvocation;
/**
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
class WebSecurityExpressionRoot extends SecurityExpressionRoot {
private FilterInvocation filterInvocation;
WebSecurityExpressionRoot(Authentication a, FilterInvocation fi) {
super(a);
this.filterInvocation = fi;
}
}

View File

@ -0,0 +1,60 @@
package org.springframework.security.expression.web;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.expression.SecurityExpressionHandler;
import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.RequestKey;
import org.springframework.security.util.UrlMatcher;
import org.springframework.util.Assert;
/**
* Expression-based <tt>FilterInvocationDefinitionSource</tt>.
*
* @author Luke Taylor
* @version $Id$
* @since 2.5
*/
public final class ExpressionBasedFilterInvocationDefinitionSource extends DefaultFilterInvocationDefinitionSource {
private final static Log logger = LogFactory.getLog(ExpressionBasedFilterInvocationDefinitionSource.class);
public ExpressionBasedFilterInvocationDefinitionSource(UrlMatcher urlMatcher,
LinkedHashMap<RequestKey, List<ConfigAttribute>> requestMap, SecurityExpressionHandler expressionHandler) {
super(urlMatcher, processMap(requestMap, expressionHandler.getExpressionParser()));
Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required");
}
private static LinkedHashMap<RequestKey, List<ConfigAttribute>> processMap(
LinkedHashMap<RequestKey, List<ConfigAttribute>> requestMap, ExpressionParser parser) {
Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object");
LinkedHashMap<RequestKey, List<ConfigAttribute>> requestToExpressionAttributesMap =
new LinkedHashMap<RequestKey, List<ConfigAttribute>>(requestMap);
for (Map.Entry<RequestKey, List<ConfigAttribute>> entry : requestMap.entrySet()) {
RequestKey request = entry.getKey();
Assert.isTrue(entry.getValue().size() == 1, "Expected a single expression attribute for " + request);
ArrayList<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(1);
String expression = entry.getValue().get(0).getAttribute();
logger.debug("Adding web access control expression '" + expression + "', for " + request);
try {
attributes.add(new WebExpressionConfigAttribute(parser.parseExpression(expression)));
} catch (ParseException e) {
throw new IllegalArgumentException("Failed to parse expression '" + expression + "'");
}
requestToExpressionAttributesMap.put(request, attributes);
}
return requestToExpressionAttributesMap;
}
}

View File

@ -1,8 +1,6 @@
package org.springframework.security.expression.web;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.SpelExpressionParser;
import org.springframework.security.ConfigAttribute;
/**
@ -15,10 +13,6 @@ import org.springframework.security.ConfigAttribute;
class WebExpressionConfigAttribute implements ConfigAttribute {
private final Expression authorizeExpression;
public WebExpressionConfigAttribute(String authorizeExpression) throws ParseException {
this.authorizeExpression = new SpelExpressionParser().parseExpression(authorizeExpression);
}
public WebExpressionConfigAttribute(Expression authorizeExpression) {
this.authorizeExpression = authorizeExpression;
}

View File

@ -1,11 +1,6 @@
package org.springframework.security.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML;
import java.lang.reflect.Method;
@ -19,21 +14,24 @@ import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.ConfigAttribute;
import org.springframework.security.MockAuthenticationEntryPoint;
import org.springframework.security.MockFilterChain;
import org.springframework.security.SecurityConfig;
import org.springframework.security.concurrent.ConcurrentLoginException;
import org.springframework.security.concurrent.ConcurrentSessionControllerImpl;
import org.springframework.security.concurrent.ConcurrentSessionFilter;
import org.springframework.security.context.HttpSessionSecurityContextRepository;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.context.SecurityContextPersistenceFilter;
import org.springframework.security.intercept.web.FilterInvocation;
import org.springframework.security.intercept.web.FilterInvocationDefinitionSource;
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
import org.springframework.security.providers.TestingAuthenticationToken;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.anonymous.AnonymousProcessingFilter;
import org.springframework.security.securechannel.ChannelProcessingFilter;
@ -69,6 +67,7 @@ public class HttpSecurityBeanDefinitionParserTests {
appContext.close();
appContext = null;
}
SecurityContextHolder.clearContext();
}
@Test
@ -691,18 +690,38 @@ public class HttpSecurityBeanDefinitionParserTests {
}
@Test
public void createDefinedSecurityContextRepository() throws Exception {
public void expressionBasedAccessAllowsAndDeniesAccessAsExpected() throws Exception {
setContext(
"<b:bean id='repo' class='org.springframework.security.context.HttpSessionSecurityContextRepository'/>" +
"<http security-context-repository-ref='repo'>" +
" <http-basic />" +
"</http>" + AUTH_PROVIDER_XML);
" <http auto-config='true' use-expressions='true'>" +
" <intercept-url pattern='/secure*' access=\"hasRole('ROLE_A')\" />" +
" <intercept-url pattern='/**' access='permitAll()' />" +
" </http>" + AUTH_PROVIDER_XML);
FilterSecurityInterceptor fis = (FilterSecurityInterceptor) appContext.getBean(BeanIds.FILTER_SECURITY_INTERCEPTOR);
FilterInvocationDefinitionSource fids = fis.getObjectDefinitionSource();
List<? extends ConfigAttribute> attrDef = fids.getAttributes(createFilterinvocation("/secure", null));
assertEquals(1, attrDef.size());
// Try an unprotected invocation
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ROLE_A"));
fis.invoke(createFilterinvocation("/permitallurl", null));
// Try secure Url as a valid user
fis.invoke(createFilterinvocation("/securex", null));
// And as a user without the required role
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ROLE_B"));
try {
fis.invoke(createFilterinvocation("/securex", null));
fail("Expected AccessDeniedInvocation");
} catch (AccessDeniedException expected) {
}
}
private void setContext(String context) {
appContext = new InMemoryXmlApplicationContext(context);
}
@SuppressWarnings("unchecked")
private List<Filter> getFilters(String url) throws Exception {
FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
Method getFilters = fcp.getClass().getDeclaredMethod("getFilters", String.class);