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:
parent
fd3990c1f8
commit
6b4045667a
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue