Add AuthorizationManager for protect-pointcut
Closes gh-11323
This commit is contained in:
parent
db25a37320
commit
624fdfa731
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.method;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.aspectj.weaver.tools.PointcutExpression;
|
||||
import org.aspectj.weaver.tools.PointcutParser;
|
||||
import org.aspectj.weaver.tools.PointcutPrimitive;
|
||||
|
||||
import org.springframework.aop.ClassFilter;
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.aop.Pointcut;
|
||||
|
||||
class AspectJMethodMatcher implements MethodMatcher, ClassFilter, Pointcut {
|
||||
|
||||
private static final PointcutParser parser;
|
||||
|
||||
static {
|
||||
Set<PointcutPrimitive> supportedPrimitives = new HashSet<>(3);
|
||||
supportedPrimitives.add(PointcutPrimitive.EXECUTION);
|
||||
supportedPrimitives.add(PointcutPrimitive.ARGS);
|
||||
supportedPrimitives.add(PointcutPrimitive.REFERENCE);
|
||||
parser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(
|
||||
supportedPrimitives);
|
||||
}
|
||||
|
||||
private final PointcutExpression expression;
|
||||
|
||||
AspectJMethodMatcher(String expression) {
|
||||
this.expression = parser.parsePointcutExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Class<?> clazz) {
|
||||
return this.expression.couldMatchJoinPointsInType(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass) {
|
||||
return this.expression.matchesMethodExecution(method).alwaysMatches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRuntime() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass, Object... args) {
|
||||
return matches(method, targetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassFilter getClassFilter() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodMatcher getMethodMatcher() {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,11 +16,17 @@
|
|||
|
||||
package org.springframework.security.config.method;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.config.AopNamespaceUtils;
|
||||
import org.springframework.aop.support.Pointcuts;
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
|
@ -28,6 +34,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
|
|||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
@ -37,6 +44,7 @@ import org.springframework.security.access.expression.method.MethodSecurityExpre
|
|||
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.Jsr250AuthorizationManager;
|
||||
import org.springframework.security.authorization.method.MethodExpressionAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
|
||||
|
@ -64,7 +72,11 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
|
|||
|
||||
private static final String ATT_USE_PREPOST = "pre-post-enabled";
|
||||
|
||||
private static final String ATT_REF = "ref";
|
||||
private static final String ATT_AUTHORIZATION_MGR = "authorization-manager-ref";
|
||||
|
||||
private static final String ATT_ACCESS = "access";
|
||||
|
||||
private static final String ATT_EXPRESSION = "expression";
|
||||
|
||||
private static final String ATT_SECURITY_CONTEXT_HOLDER_STRATEGY_REF = "security-context-holder-strategy-ref";
|
||||
|
||||
|
@ -95,7 +107,7 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
|
|||
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
|
||||
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
|
||||
if (expressionHandlerElt != null) {
|
||||
String expressionHandlerRef = expressionHandlerElt.getAttribute(ATT_REF);
|
||||
String expressionHandlerRef = expressionHandlerElt.getAttribute("ref");
|
||||
preFilterInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
|
||||
preAuthorizeInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
|
||||
postAuthorizeInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
|
||||
|
@ -137,6 +149,21 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
|
|||
pc.getRegistry().registerBeanDefinition("jsr250AuthorizationMethodInterceptor",
|
||||
jsr250Interceptor.getBeanDefinition());
|
||||
}
|
||||
Map<Pointcut, BeanMetadataElement> managers = new ManagedMap<>();
|
||||
List<Element> methods = DomUtils.getChildElementsByTagName(element, Elements.PROTECT_POINTCUT);
|
||||
if (!methods.isEmpty()) {
|
||||
for (Element protectElt : methods) {
|
||||
managers.put(pointcut(protectElt), authorizationManager(element, protectElt));
|
||||
}
|
||||
BeanDefinitionBuilder protectPointcutInterceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(AuthorizationManagerBeforeMethodInterceptor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
|
||||
.addConstructorArgValue(pointcut(managers.keySet()))
|
||||
.addConstructorArgValue(authorizationManager(managers));
|
||||
pc.getRegistry().registerBeanDefinition("protectPointcutInterceptor",
|
||||
protectPointcutInterceptor.getBeanDefinition());
|
||||
}
|
||||
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(pc, element);
|
||||
pc.popAndRegisterContainingComponent();
|
||||
return null;
|
||||
|
@ -150,6 +177,47 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
|
|||
return BeanDefinitionBuilder.rootBeanDefinition(SecurityContextHolderStrategyFactory.class).getBeanDefinition();
|
||||
}
|
||||
|
||||
private Pointcut pointcut(Element protectElt) {
|
||||
String expression = protectElt.getAttribute(ATT_EXPRESSION);
|
||||
expression = replaceBooleanOperators(expression);
|
||||
return new AspectJMethodMatcher(expression);
|
||||
}
|
||||
|
||||
private Pointcut pointcut(Collection<Pointcut> pointcuts) {
|
||||
Pointcut result = null;
|
||||
for (Pointcut pointcut : pointcuts) {
|
||||
if (result == null) {
|
||||
result = pointcut;
|
||||
}
|
||||
else {
|
||||
result = Pointcuts.union(result, pointcut);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String replaceBooleanOperators(String expression) {
|
||||
expression = StringUtils.replace(expression, " and ", " && ");
|
||||
expression = StringUtils.replace(expression, " or ", " || ");
|
||||
expression = StringUtils.replace(expression, " not ", " ! ");
|
||||
return expression;
|
||||
}
|
||||
|
||||
private BeanMetadataElement authorizationManager(Element element, Element protectElt) {
|
||||
String authorizationManager = element.getAttribute(ATT_AUTHORIZATION_MGR);
|
||||
if (StringUtils.hasText(authorizationManager)) {
|
||||
return new RuntimeBeanReference(authorizationManager);
|
||||
}
|
||||
String access = protectElt.getAttribute(ATT_ACCESS);
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(MethodExpressionAuthorizationManager.class)
|
||||
.addConstructorArgValue(access).getBeanDefinition();
|
||||
}
|
||||
|
||||
private BeanMetadataElement authorizationManager(Map<Pointcut, BeanMetadataElement> managers) {
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(PointcutDelegatingAuthorizationManager.class)
|
||||
.addConstructorArgValue(managers).getBeanDefinition();
|
||||
}
|
||||
|
||||
public static final class MethodSecurityExpressionHandlerBean
|
||||
implements FactoryBean<MethodSecurityExpressionHandler>, ApplicationContextAware {
|
||||
|
||||
|
|
|
@ -202,8 +202,8 @@ msmds.attlist &= id?
|
|||
msmds.attlist &= use-expressions?
|
||||
|
||||
method-security =
|
||||
## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with Spring Security annotations. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. Interceptors are invoked in the order specified in AuthorizationInterceptorsOrder. Use can create your own interceptors using Spring AOP.
|
||||
element method-security {method-security.attlist, expression-handler?}
|
||||
## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with Spring Security annotations. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. Interceptors are invoked in the order specified in AuthorizationInterceptorsOrder. Use can create your own interceptors using Spring AOP. Also, annotation-based interception can be overridden by expressions listed in <protect-pointcut> elements.
|
||||
element method-security {method-security.attlist, expression-handler?, protect-pointcut*}
|
||||
method-security.attlist &=
|
||||
## Specifies whether the use of Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this application context. Defaults to "true".
|
||||
attribute pre-post-enabled {xsd:boolean}?
|
||||
|
|
|
@ -615,6 +615,8 @@
|
|||
there is a match, the beans will automatically be proxied and security authorization
|
||||
applied to the methods accordingly. Interceptors are invoked in the order specified in
|
||||
AuthorizationInterceptorsOrder. Use can create your own interceptors using Spring AOP.
|
||||
Also, annotation-based interception can be overridden by expressions listed in
|
||||
<protect-pointcut> elements.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
|
@ -630,6 +632,17 @@
|
|||
<xs:attributeGroup ref="security:ref"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="unbounded" name="protect-pointcut">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines a protected pointcut and the access control configuration attributes that apply to
|
||||
it. Every bean registered in the Spring application context that provides a method that
|
||||
matches the pointcut will receive security authorization.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:attributeGroup ref="security:protect-pointcut.attlist"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attributeGroup ref="security:method-security.attlist"/>
|
||||
</xs:complexType>
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.springframework.lang.Nullable;
|
|||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.annotation.BusinessService;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
|
@ -42,6 +43,7 @@ import org.springframework.security.config.annotation.method.configuration.Metho
|
|||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
|
@ -401,6 +403,28 @@ public class MethodSecurityBeanDefinitionParserTests {
|
|||
.isThrownBy(() -> this.businessService.repeatedAnnotations());
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void supportsMethodArgumentsInPointcut() {
|
||||
this.spring.configLocations(xml("ProtectPointcut")).autowire();
|
||||
this.businessService.someOther(0);
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.businessService.someOther("somestring"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsBooleanPointcutExpressions() {
|
||||
this.spring.configLocations(xml("ProtectPointcutBoolean")).autowire();
|
||||
this.businessService.someOther("somestring");
|
||||
// All others should require ROLE_USER
|
||||
assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class)
|
||||
.isThrownBy(() -> this.businessService.someOther(0));
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new TestingAuthenticationToken("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER")));
|
||||
this.businessService.someOther(0);
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
private static String xml(String configName) {
|
||||
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2002-2021 the original author or authors.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<b:bean class="org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl"/>
|
||||
|
||||
<method-security>
|
||||
<protect-pointcut expression="execution(* org.springframework.security.access.annotation.BusinessService.someOther(String))" access="hasRole('ADMIN')"/>
|
||||
<protect-pointcut expression="execution(* org.springframework.security.access.annotation.BusinessService.*(..))" access="hasRole('USER')"/>
|
||||
</method-security>
|
||||
</b:beans>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2002-2021 the original author or authors.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<b:bean class="org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl"/>
|
||||
|
||||
<method-security>
|
||||
<protect-pointcut expression="execution(* org.springframework.security.access.annotation.BusinessService.*(..)) and not execution(* org.springframework.security.access.annotation.BusinessService.someOther(String)))" access="hasRole('USER')"/>
|
||||
</method-security>
|
||||
</b:beans>
|
|
@ -37,6 +37,7 @@ Defaults to the value returned by SecurityContextHolder.getContextHolderStrategy
|
|||
=== Child Elements of <method-security>
|
||||
|
||||
* xref:servlet/appendix/namespace/http.adoc#nsa-expression-handler[expression-handler]
|
||||
* <<nsa-protect-pointcut,protect-pointcut>>
|
||||
|
||||
[[nsa-global-method-security]]
|
||||
== <global-method-security>
|
||||
|
@ -244,6 +245,7 @@ You can find an example in the xref:servlet/authorization/method-security.adoc#n
|
|||
|
||||
|
||||
* <<nsa-global-method-security,global-method-security>>
|
||||
* <<nsa-method-security,method-security>>
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue