MethodSecurityBeanDefinitionParser.java
/*
* Copyright 2004-present 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.util.Collection;
import java.util.List;
import java.util.Map;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
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;
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.BeanDefinitionRegistry;
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;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
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.MethodInvocationResult;
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
import org.springframework.security.authorization.method.SecuredAuthorizationManager;
import org.springframework.security.config.Elements;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Processes the top-level "method-security" element.
*
* @author Josh Cummings
* @author Ngoc Nhan
* @since 5.6
*/
public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
private final Log logger = LogFactory.getLog(getClass());
private static final String ATT_USE_JSR250 = "jsr250-enabled";
private static final String ATT_USE_SECURED = "secured-enabled";
private static final String ATT_USE_PREPOST = "pre-post-enabled";
private static final String ATT_AUTHORIZATION_MGR = "authorization-manager-ref";
private static final String ATT_OBSERVATION_REGISTRY_REF = "observation-registry-ref";
private static final String ATT_ACCESS = "access";
private static final String ATT_EXPRESSION = "expression";
private static final String ATT_MODE = "mode";
private static final String ATT_SECURITY_CONTEXT_HOLDER_STRATEGY_REF = "security-context-holder-strategy-ref";
@Override
public BeanDefinition parse(Element element, ParserContext pc) {
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
pc.extractSource(element));
pc.pushContainingComponent(compositeDef);
BeanMetadataElement securityContextHolderStrategy = getSecurityContextHolderStrategy(element);
BeanMetadataElement observationRegistry = getObservationRegistry(element);
boolean prePostAnnotationsEnabled = !element.hasAttribute(ATT_USE_PREPOST)
|| "true".equals(element.getAttribute(ATT_USE_PREPOST));
boolean useAspectJ = "aspectj".equals(element.getAttribute(ATT_MODE));
if (prePostAnnotationsEnabled) {
BeanDefinitionBuilder preFilterInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PreFilterAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
BeanDefinitionBuilder preAuthorizeInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PreAuthorizeAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
.addPropertyValue("observationRegistry", observationRegistry);
BeanDefinitionBuilder postAuthorizeInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PostAuthorizeAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
.addPropertyValue("observationRegistry", observationRegistry);
BeanDefinitionBuilder postFilterInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PostFilterAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
if (expressionHandlerElt != null) {
String expressionHandlerRef = expressionHandlerElt.getAttribute("ref");
preFilterInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
preAuthorizeInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
postAuthorizeInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
postFilterInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
}
else {
BeanDefinition expressionHandler = BeanDefinitionBuilder
.rootBeanDefinition(MethodSecurityExpressionHandlerBean.class)
.getBeanDefinition();
preFilterInterceptor.addPropertyValue("expressionHandler", expressionHandler);
preAuthorizeInterceptor.addPropertyValue("expressionHandler", expressionHandler);
postAuthorizeInterceptor.addPropertyValue("expressionHandler", expressionHandler);
postFilterInterceptor.addPropertyValue("expressionHandler", expressionHandler);
}
pc.getRegistry()
.registerBeanDefinition("preFilterAuthorizationMethodInterceptor",
preFilterInterceptor.getBeanDefinition());
pc.getRegistry()
.registerBeanDefinition("preAuthorizeAuthorizationMethodInterceptor",
preAuthorizeInterceptor.getBeanDefinition());
pc.getRegistry()
.registerBeanDefinition("postAuthorizeAuthorizationMethodInterceptor",
postAuthorizeInterceptor.getBeanDefinition());
pc.getRegistry()
.registerBeanDefinition("postFilterAuthorizationMethodInterceptor",
postFilterInterceptor.getBeanDefinition());
}
boolean securedEnabled = "true".equals(element.getAttribute(ATT_USE_SECURED));
if (securedEnabled) {
BeanDefinitionBuilder securedInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(SecuredAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
.addPropertyValue("observationRegistry", observationRegistry);
pc.getRegistry()
.registerBeanDefinition("securedAuthorizationMethodInterceptor",
securedInterceptor.getBeanDefinition());
}
boolean jsr250Enabled = "true".equals(element.getAttribute(ATT_USE_JSR250));
if (jsr250Enabled) {
BeanDefinitionBuilder jsr250Interceptor = BeanDefinitionBuilder
.rootBeanDefinition(Jsr250AuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
.addPropertyValue("observationRegistry", observationRegistry);
pc.getRegistry()
.registerBeanDefinition("jsr250AuthorizationMethodInterceptor", jsr250Interceptor.getBeanDefinition());
}
Map<Pointcut, BeanMetadataElement> managers = new ManagedMap<>();
List<Element> methods = DomUtils.getChildElementsByTagName(element, Elements.PROTECT_POINTCUT);
if (useAspectJ) {
if (!methods.isEmpty()) {
pc.getReaderContext()
.error("Cannot use <protect-pointcut> and mode='aspectj' together", pc.extractSource(element));
}
registerInterceptors(pc.getRegistry());
}
else {
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;
}
private BeanMetadataElement getObservationRegistry(Element methodSecurityElmt) {
String holderStrategyRef = methodSecurityElmt.getAttribute(ATT_OBSERVATION_REGISTRY_REF);
if (StringUtils.hasText(holderStrategyRef)) {
return new RuntimeBeanReference(holderStrategyRef);
}
return BeanDefinitionBuilder.rootBeanDefinition(ObservationRegistryFactory.class).getBeanDefinition();
}
private BeanMetadataElement getSecurityContextHolderStrategy(Element methodSecurityElmt) {
String holderStrategyRef = methodSecurityElmt.getAttribute(ATT_SECURITY_CONTEXT_HOLDER_STRATEGY_REF);
if (StringUtils.hasText(holderStrategyRef)) {
return new RuntimeBeanReference(holderStrategyRef);
}
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();
}
private void registerInterceptors(BeanDefinitionRegistry registry) {
registerBeanDefinition("preFilterAuthorizationMethodInterceptor",
"org.springframework.security.authorization.method.aspectj.PreFilterAspect", "preFilterAspect$0",
registry);
registerBeanDefinition("postFilterAuthorizationMethodInterceptor",
"org.springframework.security.authorization.method.aspectj.PostFilterAspect", "postFilterAspect$0",
registry);
registerBeanDefinition("preAuthorizeAuthorizationMethodInterceptor",
"org.springframework.security.authorization.method.aspectj.PreAuthorizeAspect", "preAuthorizeAspect$0",
registry);
registerBeanDefinition("postAuthorizeAuthorizationMethodInterceptor",
"org.springframework.security.authorization.method.aspectj.PostAuthorizeAspect",
"postAuthorizeAspect$0", registry);
registerBeanDefinition("securedAuthorizationMethodInterceptor",
"org.springframework.security.authorization.method.aspectj.SecuredAspect", "securedAspect$0", registry);
}
private void registerBeanDefinition(String beanName, String aspectClassName, String aspectBeanName,
BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(beanName)) {
return;
}
BeanDefinition interceptor = registry.getBeanDefinition(beanName);
BeanDefinitionBuilder aspect = BeanDefinitionBuilder.rootBeanDefinition(aspectClassName);
aspect.setFactoryMethod("aspectOf");
aspect.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
aspect.addPropertyValue("securityInterceptor", interceptor);
registry.registerBeanDefinition(aspectBeanName, aspect.getBeanDefinition());
}
public static final class MethodSecurityExpressionHandlerBean
implements FactoryBean<MethodSecurityExpressionHandler>, ApplicationContextAware {
private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
@Override
public MethodSecurityExpressionHandler getObject() {
return this.expressionHandler;
}
@Override
public Class<?> getObjectType() {
return MethodSecurityExpressionHandler.class;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
applicationContext.getBeanProvider(GrantedAuthorityDefaults.class)
.ifUnique((grantedAuthorityDefaults) -> this.expressionHandler
.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix()));
}
}
public static final class Jsr250AuthorizationMethodInterceptor
implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor>, ApplicationContextAware {
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private final Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
@Override
public AuthorizationManagerBeforeMethodInterceptor getObject() {
AuthorizationManager<MethodInvocation> manager = this.manager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, this.manager);
}
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.jsr250(manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
}
@Override
public Class<?> getObjectType() {
return AuthorizationManagerBeforeMethodInterceptor.class;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
applicationContext.getBeanProvider(GrantedAuthorityDefaults.class)
.ifUnique((grantedAuthorityDefaults) -> this.manager
.setRolePrefix(grantedAuthorityDefaults.getRolePrefix()));
}
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
}
public static final class SecuredAuthorizationMethodInterceptor
implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor> {
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private final SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
@Override
public AuthorizationManagerBeforeMethodInterceptor getObject() {
AuthorizationManager<MethodInvocation> manager = this.manager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, this.manager);
}
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.secured(manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
}
@Override
public Class<?> getObjectType() {
return AuthorizationManagerBeforeMethodInterceptor.class;
}
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
}
public static final class PreAuthorizeAuthorizationMethodInterceptor
implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor> {
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private final PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
@Override
public AuthorizationManagerBeforeMethodInterceptor getObject() {
AuthorizationManager<MethodInvocation> manager = this.manager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, this.manager);
}
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
}
@Override
public Class<?> getObjectType() {
return AuthorizationManagerBeforeMethodInterceptor.class;
}
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.manager.setExpressionHandler(expressionHandler);
}
public void setObservationRegistry(ObservationRegistry registry) {
this.observationRegistry = registry;
}
}
public static final class PostAuthorizeAuthorizationMethodInterceptor
implements FactoryBean<AuthorizationManagerAfterMethodInterceptor> {
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private final PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
@Override
public AuthorizationManagerAfterMethodInterceptor getObject() {
AuthorizationManager<MethodInvocationResult> manager = this.manager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, this.manager);
}
AuthorizationManagerAfterMethodInterceptor interceptor = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
}
@Override
public Class<?> getObjectType() {
return AuthorizationManagerAfterMethodInterceptor.class;
}
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
this.manager.setExpressionHandler(expressionHandler);
}
public void setObservationRegistry(ObservationRegistry registry) {
this.observationRegistry = registry;
}
}
static class SecurityContextHolderStrategyFactory implements FactoryBean<SecurityContextHolderStrategy> {
@Override
public SecurityContextHolderStrategy getObject() throws Exception {
return SecurityContextHolder.getContextHolderStrategy();
}
@Override
public Class<?> getObjectType() {
return SecurityContextHolderStrategy.class;
}
}
static class ObservationRegistryFactory implements FactoryBean<ObservationRegistry> {
@Override
public ObservationRegistry getObject() throws Exception {
return ObservationRegistry.NOOP;
}
@Override
public Class<?> getObjectType() {
return ObservationRegistry.class;
}
}
}