diff --git a/config/src/main/java/org/springframework/security/config/annotation/ObjectPostProcessor.java b/config/src/main/java/org/springframework/security/config/annotation/ObjectPostProcessor.java index 53a43d1ce9..7d26a0c4bd 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/ObjectPostProcessor.java +++ b/config/src/main/java/org/springframework/security/config/annotation/ObjectPostProcessor.java @@ -31,6 +31,15 @@ import org.springframework.beans.factory.InitializingBean; */ public interface ObjectPostProcessor { + static ObjectPostProcessor identity() { + return new ObjectPostProcessor<>() { + @Override + public O postProcess(O object) { + return object; + } + }; + } + /** * Initialize the object possibly returning a modified instance that should be used * instead. diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java index 97f529efd4..ff55c9d48e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java @@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.method.configuration; import java.util.function.Supplier; -import io.micrometer.observation.ObservationRegistry; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -36,9 +35,9 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.authorization.AuthoritiesAuthorizationManager; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.ObservationAuthorizationManager; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.authorization.method.Jsr250AuthorizationManager; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.core.context.SecurityContextHolderStrategy; @@ -58,8 +57,15 @@ final class Jsr250MethodSecurityConfiguration implements ImportAware, AopInfrast private final Jsr250AuthorizationManager authorizationManager = new Jsr250AuthorizationManager(); - private AuthorizationManagerBeforeMethodInterceptor methodInterceptor = AuthorizationManagerBeforeMethodInterceptor - .jsr250(this.authorizationManager); + private final AuthorizationManagerBeforeMethodInterceptor methodInterceptor; + + Jsr250MethodSecurityConfiguration( + ObjectProvider>> postProcessors) { + ObjectPostProcessor> postProcessor = postProcessors + .getIfUnique(ObjectPostProcessor::identity); + AuthorizationManager manager = postProcessor.postProcess(this.authorizationManager); + this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.jsr250(manager); + } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @@ -95,16 +101,6 @@ final class Jsr250MethodSecurityConfiguration implements ImportAware, AopInfrast this.methodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy); } - @Autowired(required = false) - void setObservationRegistry(ObservationRegistry registry) { - if (registry.isNoop()) { - return; - } - AuthorizationManager observed = new ObservationAuthorizationManager<>(registry, - this.authorizationManager); - this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.secured(observed); - } - @Autowired(required = false) void setEventPublisher(AuthorizationEventPublisher eventPublisher) { this.methodInterceptor.setAuthorizationEventPublisher(eventPublisher); diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java index 2ceb262a14..103ea5c8f6 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java @@ -41,6 +41,9 @@ final class MethodSecuritySelector implements ImportSelector { private static final boolean isDataPresent = ClassUtils .isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null); + private static final boolean isObservabilityPresent = ClassUtils + .isPresent("io.micrometer.observation.ObservationRegistry", null); + private final ImportSelector autoProxy = new AutoProxyRegistrarSelector(); @Override @@ -64,6 +67,10 @@ final class MethodSecuritySelector implements ImportSelector { if (isDataPresent) { imports.add(AuthorizationProxyDataConfiguration.class.getName()); } + if (isObservabilityPresent) { + imports.add( + "org.springframework.security.config.annotation.observation.configuration.ObservationConfiguration"); + } return imports.toArray(new String[0]); } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java index 3426529d48..98dcfef04b 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java @@ -16,8 +16,8 @@ package org.springframework.security.config.annotation.method.configuration; -import io.micrometer.observation.ObservationRegistry; import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.Pointcut; import org.springframework.aop.framework.AopInfrastructureBean; @@ -38,14 +38,16 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.aot.hint.PrePostAuthorizeHintsRegistrar; import org.springframework.security.aot.hint.SecurityHintsRegistrar; import org.springframework.security.authorization.AuthorizationEventPublisher; -import org.springframework.security.authorization.ObservationAuthorizationManager; +import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; +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.PrePostTemplateDefaults; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.context.SecurityContextHolderStrategy; @@ -78,21 +80,29 @@ final class PrePostMethodSecurityConfiguration implements ImportAware, Applicati private final PreFilterAuthorizationMethodInterceptor preFilterMethodInterceptor = new PreFilterAuthorizationMethodInterceptor(); - private AuthorizationManagerBeforeMethodInterceptor preAuthorizeMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor - .preAuthorize(this.preAuthorizeAuthorizationManager); + private final AuthorizationManagerBeforeMethodInterceptor preAuthorizeMethodInterceptor; - private AuthorizationManagerAfterMethodInterceptor postAuthorizeMethodInterceptor = AuthorizationManagerAfterMethodInterceptor - .postAuthorize(this.postAuthorizeAuthorizationManager); + private final AuthorizationManagerAfterMethodInterceptor postAuthorizeMethodInterceptor; private final PostFilterAuthorizationMethodInterceptor postFilterMethodInterceptor = new PostFilterAuthorizationMethodInterceptor(); private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); - { + PrePostMethodSecurityConfiguration( + ObjectProvider>> preAuthorizeProcessor, + ObjectProvider>> postAuthorizeProcessor) { this.preFilterMethodInterceptor.setExpressionHandler(this.expressionHandler); this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler); this.postAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler); this.postFilterMethodInterceptor.setExpressionHandler(this.expressionHandler); + AuthorizationManager preAuthorize = preAuthorizeProcessor + .getIfUnique(ObjectPostProcessor::identity) + .postProcess(this.preAuthorizeAuthorizationManager); + this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize(preAuthorize); + AuthorizationManager postAuthorize = postAuthorizeProcessor + .getIfUnique(ObjectPostProcessor::identity) + .postProcess(this.postAuthorizeAuthorizationManager); + this.postAuthorizeMethodInterceptor = AuthorizationManagerAfterMethodInterceptor.postAuthorize(postAuthorize); } @Override @@ -144,17 +154,6 @@ final class PrePostMethodSecurityConfiguration implements ImportAware, Applicati this.postFilterMethodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy); } - @Autowired(required = false) - void setObservationRegistry(ObservationRegistry registry) { - if (registry.isNoop()) { - return; - } - this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor - .preAuthorize(new ObservationAuthorizationManager<>(registry, this.preAuthorizeAuthorizationManager)); - this.postAuthorizeMethodInterceptor = AuthorizationManagerAfterMethodInterceptor - .postAuthorize(new ObservationAuthorizationManager<>(registry, this.postAuthorizeAuthorizationManager)); - } - @Autowired(required = false) void setAuthorizationEventPublisher(AuthorizationEventPublisher publisher) { this.preAuthorizeMethodInterceptor.setAuthorizationEventPublisher(publisher); diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java index 995e57d589..8df92c4ee0 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java @@ -16,8 +16,8 @@ package org.springframework.security.config.annotation.method.configuration; -import io.micrometer.observation.ObservationRegistry; import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.Pointcut; import org.springframework.aop.framework.AopInfrastructureBean; @@ -34,14 +34,16 @@ import org.springframework.context.annotation.Role; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.authentication.ReactiveAuthenticationManager; -import org.springframework.security.authorization.ObservationReactiveAuthorizationManager; +import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor; import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor; +import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.PostAuthorizeReactiveAuthorizationManager; import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor; import org.springframework.security.authorization.method.PreAuthorizeReactiveAuthorizationManager; import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor; import org.springframework.security.authorization.method.PrePostTemplateDefaults; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; @@ -77,22 +79,30 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration private PostFilterAuthorizationReactiveMethodInterceptor postFilterMethodInterceptor = new PostFilterAuthorizationReactiveMethodInterceptor(); - private AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeMethodInterceptor; + private final AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeMethodInterceptor; - private AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeMethodInterceptor; + private final AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeMethodInterceptor; @Autowired(required = false) - ReactiveAuthorizationManagerMethodSecurityConfiguration(MethodSecurityExpressionHandler expressionHandler) { + ReactiveAuthorizationManagerMethodSecurityConfiguration(MethodSecurityExpressionHandler expressionHandler, + ObjectProvider>> preAuthorizePostProcessor, + ObjectProvider>> postAuthorizePostProcessor) { if (expressionHandler != null) { this.preFilterMethodInterceptor = new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler); this.preAuthorizeAuthorizationManager = new PreAuthorizeReactiveAuthorizationManager(expressionHandler); this.postFilterMethodInterceptor = new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler); this.postAuthorizeAuthorizationManager = new PostAuthorizeReactiveAuthorizationManager(expressionHandler); } + ReactiveAuthorizationManager preAuthorize = preAuthorizePostProcessor + .getIfUnique(ObjectPostProcessor::identity) + .postProcess(this.preAuthorizeAuthorizationManager); this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeReactiveMethodInterceptor - .preAuthorize(this.preAuthorizeAuthorizationManager); + .preAuthorize(preAuthorize); + ReactiveAuthorizationManager postAuthorize = postAuthorizePostProcessor + .getIfAvailable(ObjectPostProcessor::identity) + .postProcess(this.postAuthorizeAuthorizationManager); this.postAuthorizeMethodInterceptor = AuthorizationManagerAfterReactiveMethodInterceptor - .postAuthorize(this.postAuthorizeAuthorizationManager); + .postAuthorize(postAuthorize); } @Override @@ -117,17 +127,6 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration this.postFilterMethodInterceptor.setTemplateDefaults(templateDefaults); } - @Autowired(required = false) - void setObservationRegistry(ObservationRegistry registry) { - if (registry.isNoop()) { - return; - } - this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize( - new ObservationReactiveAuthorizationManager<>(registry, this.preAuthorizeAuthorizationManager)); - this.postAuthorizeMethodInterceptor = AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize( - new ObservationReactiveAuthorizationManager<>(registry, this.postAuthorizeAuthorizationManager)); - } - @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor preFilterAuthorizationMethodInterceptor( diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java index dbedbeab60..aa99988e27 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java @@ -38,6 +38,9 @@ class ReactiveMethodSecuritySelector implements ImportSelector { private static final boolean isDataPresent = ClassUtils .isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null); + private static final boolean isObservabilityPresent = ClassUtils + .isPresent("io.micrometer.observation.ObservationRegistry", null); + private final ImportSelector autoProxy = new AutoProxyRegistrarSelector(); @Override @@ -58,6 +61,10 @@ class ReactiveMethodSecuritySelector implements ImportSelector { if (isDataPresent) { imports.add(AuthorizationProxyDataConfiguration.class.getName()); } + if (isObservabilityPresent) { + imports.add( + "org.springframework.security.config.annotation.observation.configuration.ReactiveObservationConfiguration"); + } imports.add(AuthorizationProxyConfiguration.class.getName()); return imports.toArray(new String[0]); } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java index 7247e8044d..1950fe84f5 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java @@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.method.configuration; import java.util.function.Supplier; -import io.micrometer.observation.ObservationRegistry; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -37,9 +36,9 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.authorization.AuthoritiesAuthorizationManager; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.ObservationAuthorizationManager; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.authorization.method.SecuredAuthorizationManager; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.core.context.SecurityContextHolderStrategy; /** @@ -58,8 +57,15 @@ final class SecuredMethodSecurityConfiguration implements ImportAware, AopInfras private final SecuredAuthorizationManager authorizationManager = new SecuredAuthorizationManager(); - private AuthorizationManagerBeforeMethodInterceptor methodInterceptor = AuthorizationManagerBeforeMethodInterceptor - .secured(this.authorizationManager); + private final AuthorizationManagerBeforeMethodInterceptor methodInterceptor; + + SecuredMethodSecurityConfiguration( + ObjectProvider>> postProcessors) { + ObjectPostProcessor> postProcessor = postProcessors + .getIfUnique(ObjectPostProcessor::identity); + AuthorizationManager manager = postProcessor.postProcess(this.authorizationManager); + this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.secured(manager); + } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @@ -90,16 +96,6 @@ final class SecuredMethodSecurityConfiguration implements ImportAware, AopInfras this.methodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy); } - @Autowired(required = false) - void setObservationRegistry(ObservationRegistry registry) { - if (registry.isNoop()) { - return; - } - AuthorizationManager observed = new ObservationAuthorizationManager<>(registry, - this.authorizationManager); - this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.secured(observed); - } - @Autowired(required = false) void setEventPublisher(AuthorizationEventPublisher eventPublisher) { this.methodInterceptor.setAuthorizationEventPublisher(eventPublisher); diff --git a/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/AbstractObservationObjectPostProcessor.java b/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/AbstractObservationObjectPostProcessor.java new file mode 100644 index 0000000000..a9b58a98c3 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/AbstractObservationObjectPostProcessor.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2024 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.annotation.observation.configuration; + +import java.util.function.BiFunction; +import java.util.function.Function; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.security.config.annotation.ObjectPostProcessor; + +abstract class AbstractObservationObjectPostProcessor implements ObjectPostProcessor { + + private final ObjectProvider observationRegistry; + + private final BiFunction wrapper; + + AbstractObservationObjectPostProcessor(ObjectProvider observationRegistry, + Function constructor) { + this(observationRegistry, (registry, object) -> constructor.apply(registry)); + } + + AbstractObservationObjectPostProcessor(ObjectProvider observationRegistry, + BiFunction constructor) { + this.observationRegistry = observationRegistry; + this.wrapper = constructor; + } + + @Override + public O1 postProcess(O1 object) { + ObservationRegistry registry = this.observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP); + if (registry.isNoop()) { + return object; + } + return (O1) this.wrapper.apply(registry, object); + } + +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ObservationConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ObservationConfiguration.java new file mode 100644 index 0000000000..269f963cfa --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ObservationConfiguration.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2024 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.annotation.observation.configuration; + +import io.micrometer.observation.ObservationRegistry; +import jakarta.servlet.http.HttpServletRequest; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ObservationAuthenticationManager; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.authorization.ObservationAuthorizationManager; +import org.springframework.security.authorization.method.MethodInvocationResult; +import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.ObservationFilterChainDecorator; + +@Configuration(proxyBeanMethods = false) +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +class ObservationConfiguration { + + private final ObjectProvider observationRegistry; + + ObservationConfiguration(ObjectProvider observationRegistry) { + this.observationRegistry = observationRegistry; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor> methodAuthorizationManagerPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationAuthorizationManager::new) { + }; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor> methodResultAuthorizationManagerPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationAuthorizationManager::new) { + }; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor> webAuthorizationManagerPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationAuthorizationManager::new) { + }; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor authenticationManagerPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationAuthenticationManager::new) { + }; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor filterChainDecoratorPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationFilterChainDecorator::new) { + }; + } + +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ReactiveObservationConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ReactiveObservationConfiguration.java new file mode 100644 index 0000000000..f898e77d8d --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ReactiveObservationConfiguration.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2024 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.annotation.observation.configuration; + +import io.micrometer.observation.ObservationRegistry; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.security.authentication.ObservationReactiveAuthenticationManager; +import org.springframework.security.authentication.ReactiveAuthenticationManager; +import org.springframework.security.authorization.ObservationReactiveAuthorizationManager; +import org.springframework.security.authorization.ReactiveAuthorizationManager; +import org.springframework.security.authorization.method.MethodInvocationResult; +import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.web.server.ObservationWebFilterChainDecorator; +import org.springframework.security.web.server.WebFilterChainProxy; +import org.springframework.web.server.ServerWebExchange; + +@Configuration(proxyBeanMethods = false) +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +class ReactiveObservationConfiguration { + + private final ObjectProvider observationRegistry; + + ReactiveObservationConfiguration(ObjectProvider observationRegistry) { + this.observationRegistry = observationRegistry; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor> methodAuthorizationManagerPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationReactiveAuthorizationManager::new) { + }; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor> methodResultAuthorizationManagerPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationReactiveAuthorizationManager::new) { + }; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor> webAuthorizationManagerPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationReactiveAuthorizationManager::new) { + }; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor authenticationManagerPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationReactiveAuthenticationManager::new) { + }; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor filterChainDecoratorPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationWebFilterChainDecorator::new) { + }; + } + +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/WebSocketObservationConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/WebSocketObservationConfiguration.java new file mode 100644 index 0000000000..2877a0b913 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/observation/configuration/WebSocketObservationConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2024 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.annotation.observation.configuration; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.messaging.Message; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.authorization.ObservationAuthorizationManager; +import org.springframework.security.config.annotation.ObjectPostProcessor; + +@Configuration(proxyBeanMethods = false) +@Role(BeanDefinition.ROLE_INFRASTRUCTURE) +class WebSocketObservationConfiguration { + + private final ObjectProvider observationRegistry; + + WebSocketObservationConfiguration(ObjectProvider observationRegistry) { + this.observationRegistry = observationRegistry; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + ObjectPostProcessor>> messageAuthorizationManagerPostProcessor() { + return new AbstractObservationObjectPostProcessor<>(this.observationRegistry, + ObservationAuthorizationManager::new) { + }; + } + +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/rsocket/EnableRSocketSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/rsocket/EnableRSocketSecurity.java index 29058c10e6..ab46f90bd0 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/rsocket/EnableRSocketSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/rsocket/EnableRSocketSecurity.java @@ -35,7 +35,8 @@ import org.springframework.context.annotation.Import; @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Import({ RSocketSecurityConfiguration.class, SecuritySocketAcceptorInterceptorConfiguration.class }) +@Import({ RSocketSecurityConfiguration.class, SecuritySocketAcceptorInterceptorConfiguration.class, + ReactiveObservationImportSelector.class }) public @interface EnableRSocketSecurity { } diff --git a/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurityConfiguration.java index 9f7f5c9c5b..f7b23e14cf 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurityConfiguration.java @@ -16,16 +16,14 @@ package org.springframework.security.config.annotation.rsocket; -import io.micrometer.observation.ObservationRegistry; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; -import org.springframework.security.authentication.ObservationReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; @@ -46,7 +44,7 @@ class RSocketSecurityConfiguration { private PasswordEncoder passwordEncoder; - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; + private ObjectPostProcessor postProcessor = ObjectPostProcessor.identity(); @Autowired(required = false) void setAuthenticationManager(ReactiveAuthenticationManager authenticationManager) { @@ -64,8 +62,8 @@ class RSocketSecurityConfiguration { } @Autowired(required = false) - void setObservationRegistry(ObservationRegistry observationRegistry) { - this.observationRegistry = observationRegistry; + void setAuthenticationManagerPostProcessor(ObjectPostProcessor postProcessor) { + this.postProcessor = postProcessor; } @Bean(name = RSOCKET_SECURITY_BEAN_NAME) @@ -86,10 +84,7 @@ class RSocketSecurityConfiguration { if (this.passwordEncoder != null) { manager.setPasswordEncoder(this.passwordEncoder); } - if (!this.observationRegistry.isNoop()) { - return new ObservationReactiveAuthenticationManager(this.observationRegistry, manager); - } - return manager; + return this.postProcessor.postProcess(manager); } return null; } diff --git a/config/src/main/java/org/springframework/security/config/annotation/rsocket/ReactiveObservationImportSelector.java b/config/src/main/java/org/springframework/security/config/annotation/rsocket/ReactiveObservationImportSelector.java new file mode 100644 index 0000000000..9c354596ae --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/rsocket/ReactiveObservationImportSelector.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2013 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.annotation.rsocket; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.util.ClassUtils; + +/** + * Used by {@link EnableWebFluxSecurity} to conditionally import observation configuration + * when {@link ObservationRegistry} is present. + * + * @author Josh Cummings + * @since 6.4 + */ +class ReactiveObservationImportSelector implements ImportSelector { + + private static final boolean observabilityPresent; + + static { + ClassLoader classLoader = ReactiveObservationImportSelector.class.getClassLoader(); + observabilityPresent = ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", classLoader); + } + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + if (!observabilityPresent) { + return new String[0]; + } + return new String[] { + "org.springframework.security.config.annotation.observation.configuration.ReactiveObservationConfiguration" }; + } + +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 448e2083a9..542987c1b0 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import io.micrometer.observation.ObservationRegistry; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -30,12 +29,13 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.ApplicationContext; import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; +import org.springframework.core.ResolvableType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.ObservationAuthenticationManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder; import org.springframework.security.config.annotation.ObjectPostProcessor; @@ -3279,13 +3279,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder postProcessor = getAuthenticationManagerPostProcessor(); AuthenticationManager manager = getAuthenticationRegistry().build(); - if (!registry.isNoop() && manager != null) { - setSharedObject(AuthenticationManager.class, new ObservationAuthenticationManager(registry, manager)); - } - else { - setSharedObject(AuthenticationManager.class, manager); + if (manager != null) { + setSharedObject(AuthenticationManager.class, postProcessor.postProcess(manager)); } } } @@ -3723,8 +3720,12 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder ObservationRegistry.NOOP); + private ObjectPostProcessor getAuthenticationManagerPostProcessor() { + ApplicationContext context = getContext(); + ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class, + AuthenticationManager.class); + ObjectProvider> manager = context.getBeanProvider(type); + return manager.getIfUnique(ObjectPostProcessor::identity); } /** diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java index 684c5f8015..bd1c81b19f 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java @@ -28,8 +28,10 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.core.ResolvableType; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; @@ -45,8 +47,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur import org.springframework.security.core.context.SecurityContext; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.FilterChainProxy.FilterChainDecorator; import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.ObservationFilterChainDecorator; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator; import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer; @@ -110,6 +112,9 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder filterChainDecoratorPostProcessor = ObjectPostProcessor + .identity(); + private HttpServletRequestTransformer privilegeEvaluatorRequestTransformer; private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler(); @@ -403,6 +408,11 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder> postProcessor = applicationContext + .getBeanProvider(type); + this.filterChainDecoratorPostProcessor = postProcessor.getIfUnique(ObjectPostProcessor::identity); Class requestTransformerClass = HttpServletRequestTransformer.class; this.privilegeEvaluatorRequestTransformer = applicationContext.getBeanProvider(requestTransformerClass) .getIfUnique(); @@ -413,11 +423,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder> postProcessor = ObjectPostProcessor + .identity(); + /** * Creates an instance. * @param context the {@link ApplicationContext} to use @@ -87,6 +90,11 @@ public final class AuthorizeHttpRequestsConfigurer>> provider = context + .getBeanProvider(type); + provider.ifUnique((postProcessor) -> this.postProcessor = postProcessor); } /** @@ -123,17 +131,6 @@ public final class AuthorizeHttpRequestsConfigurer.hasRole('USER')"); Assert.state(this.mappingCount > 0, "At least one mapping is required (for example, authorizeHttpRequests().anyRequest().authenticated())"); - ObservationRegistry registry = getObservationRegistry(); RequestMatcherDelegatingAuthorizationManager manager = postProcess(this.managerBuilder.build()); - if (registry.isNoop()) { - return manager; - } - return new ObservationAuthorizationManager<>(registry, manager); + return AuthorizeHttpRequestsConfigurer.this.postProcessor.postProcess(manager); } @Override diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurity.java index 21ad642a07..f5f7a2ddc2 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurity.java @@ -86,7 +86,7 @@ import org.springframework.security.config.web.server.ServerHttpSecurity; @Target(ElementType.TYPE) @Documented @Import({ ServerHttpSecurityConfiguration.class, WebFluxSecurityConfiguration.class, - ReactiveOAuth2ClientImportSelector.class }) + ReactiveOAuth2ClientImportSelector.class, ReactiveObservationImportSelector.class }) public @interface EnableWebFluxSecurity { } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveObservationImportSelector.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveObservationImportSelector.java new file mode 100644 index 0000000000..fcdee93626 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveObservationImportSelector.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 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.annotation.web.reactive; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.ClassUtils; + +/** + * Used by {@link EnableWebFluxSecurity} to conditionally import observation configuration + * when {@link ObservationRegistry} is present. + * + * @author Josh Cummings + * @since 6.4 + */ +class ReactiveObservationImportSelector implements ImportSelector { + + private static final boolean observabilityPresent; + + static { + ClassLoader classLoader = ReactiveObservationImportSelector.class.getClassLoader(); + observabilityPresent = ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", classLoader); + } + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + if (!observabilityPresent) { + return new String[0]; + } + return new String[] { + "org.springframework.security.config.annotation.observation.configuration.ReactiveObservationConfiguration" }; + } + +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java index 40bbac985d..e442a2cb03 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java @@ -16,8 +16,6 @@ package org.springframework.security.config.annotation.web.reactive; -import io.micrometer.observation.ObservationRegistry; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectProvider; @@ -29,10 +27,10 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.security.authentication.ObservationReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService; @@ -67,7 +65,7 @@ class ServerHttpSecurityConfiguration { private ReactiveCompromisedPasswordChecker compromisedPasswordChecker; - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; + private ObjectPostProcessor postProcessor = ObjectPostProcessor.identity(); @Autowired(required = false) private BeanFactory beanFactory; @@ -98,8 +96,8 @@ class ServerHttpSecurityConfiguration { } @Autowired(required = false) - void setObservationRegistry(ObservationRegistry observationRegistry) { - this.observationRegistry = observationRegistry; + void setAuthenticationManagerPostProcessor(ObjectPostProcessor postProcessor) { + this.postProcessor = postProcessor; } @Autowired(required = false) @@ -169,10 +167,7 @@ class ServerHttpSecurityConfiguration { } manager.setUserDetailsPasswordService(this.userDetailsPasswordService); manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker); - if (!this.observationRegistry.isNoop()) { - return new ObservationReactiveAuthenticationManager(this.observationRegistry, manager); - } - return manager; + return this.postProcessor.postProcess(manager); } return null; } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java index 44778d921f..bc584a2eff 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java @@ -19,20 +19,20 @@ package org.springframework.security.config.annotation.web.reactive; import java.util.Arrays; import java.util.List; -import io.micrometer.observation.ObservationRegistry; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor; -import org.springframework.security.web.server.ObservationWebFilterChainDecorator; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; +import org.springframework.security.web.server.WebFilterChainProxy.DefaultWebFilterChainDecorator; +import org.springframework.security.web.server.WebFilterChainProxy.WebFilterChainDecorator; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.web.reactive.result.view.AbstractView; @@ -57,7 +57,7 @@ class WebFluxSecurityConfiguration { private List securityWebFilterChains; - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; + private ObjectPostProcessor postProcessor = ObjectPostProcessor.identity(); static { isOAuth2Present = ClassUtils.isPresent(REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME, @@ -73,17 +73,16 @@ class WebFluxSecurityConfiguration { } @Autowired(required = false) - void setObservationRegistry(ObservationRegistry observationRegistry) { - this.observationRegistry = observationRegistry; + void setFilterChainPostProcessor(ObjectPostProcessor postProcessor) { + this.postProcessor = postProcessor; } @Bean(SPRING_SECURITY_WEBFILTERCHAINFILTER_BEAN_NAME) @Order(WEB_FILTER_CHAIN_FILTER_ORDER) WebFilterChainProxy springSecurityWebFilterChainFilter() { WebFilterChainProxy proxy = new WebFilterChainProxy(getSecurityWebFilterChains()); - if (!this.observationRegistry.isNoop()) { - proxy.setFilterChainDecorator(new ObservationWebFilterChainDecorator(this.observationRegistry)); - } + WebFilterChainDecorator decorator = this.postProcessor.postProcess(new DefaultWebFilterChainDecorator()); + proxy.setFilterChainDecorator(decorator); return proxy; } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.java index 8b10c3bc23..f67f14f343 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.java @@ -52,7 +52,7 @@ import org.springframework.context.annotation.Import; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented -@Import(WebSocketMessageBrokerSecurityConfiguration.class) +@Import({ WebSocketMessageBrokerSecurityConfiguration.class, WebSocketObservationImportSelector.class }) public @interface EnableWebSocketSecurity { } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfiguration.java index b9b0aec90e..bcd2c0ee76 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfiguration.java @@ -20,8 +20,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import io.micrometer.observation.ObservationRegistry; - import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -33,8 +31,8 @@ import org.springframework.messaging.handler.invocation.HandlerMethodArgumentRes import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.ObservationAuthorizationManager; import org.springframework.security.authorization.SpringAuthorizationEventPublisher; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; @@ -79,7 +77,7 @@ final class WebSocketMessageBrokerSecurityConfiguration private AuthorizationManager> authorizationManager = ANY_MESSAGE_AUTHENTICATED; - private ObservationRegistry observationRegistry = ObservationRegistry.NOOP; + private ObjectPostProcessor>> postProcessor = ObjectPostProcessor.identity(); private ApplicationContext context; @@ -106,9 +104,7 @@ final class WebSocketMessageBrokerSecurityConfiguration } AuthorizationManager> manager = this.authorizationManager; - if (!this.observationRegistry.isNoop()) { - manager = new ObservationAuthorizationManager<>(this.observationRegistry, manager); - } + manager = this.postProcessor.postProcess(manager); AuthorizationChannelInterceptor interceptor = new AuthorizationChannelInterceptor(manager); interceptor.setAuthorizationEventPublisher(new SpringAuthorizationEventPublisher(this.context)); interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy); @@ -128,8 +124,9 @@ final class WebSocketMessageBrokerSecurityConfiguration } @Autowired(required = false) - void setObservationRegistry(ObservationRegistry observationRegistry) { - this.observationRegistry = observationRegistry; + void setMessageAuthorizationManagerPostProcessor( + ObjectPostProcessor>> postProcessor) { + this.postProcessor = postProcessor; } @Autowired(required = false) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketObservationImportSelector.java b/config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketObservationImportSelector.java new file mode 100644 index 0000000000..a8923b6dc0 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketObservationImportSelector.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 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.annotation.web.socket; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.ClassUtils; + +/** + * Used by {@link EnableWebSocketSecurity} to conditionally import observation + * configuration when {@link ObservationRegistry} is present. + * + * @author Josh Cummings + * @since 6.4 + */ +class WebSocketObservationImportSelector implements ImportSelector { + + private static final boolean observabilityPresent; + + static { + ClassLoader classLoader = WebSocketObservationImportSelector.class.getClassLoader(); + observabilityPresent = ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", classLoader); + } + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + if (!observabilityPresent) { + return new String[0]; + } + return new String[] { + "org.springframework.security.config.annotation.observation.configuration.WebSocketObservationConfiguration" }; + } + +} diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 329ece6af7..62bf9586ab 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; @@ -33,13 +34,13 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import io.micrometer.observation.ObservationRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; import reactor.util.context.Context; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.ApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; @@ -55,9 +56,9 @@ import org.springframework.security.authentication.ReactiveAuthenticationManager import org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager; import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager; import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.ObservationReactiveAuthorizationManager; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; @@ -1740,6 +1741,18 @@ public class ServerHttpSecurity { return this.context.getBeanProvider(beanClass).getIfUnique(() -> defaultInstance); } + private ObjectProvider getBeanProvider(ResolvableType type) { + if (this.context == null) { + return new ObjectProvider<>() { + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + }; + } + return this.context.getBeanProvider(type); + } + private T getBeanOrNull(Class beanClass) { return getBeanOrNull(ResolvableType.forClass(beanClass)); } @@ -1795,6 +1808,17 @@ public class ServerHttpSecurity { private PathPatternParser pathPatternParser; + private ObjectPostProcessor> postProcessor = ObjectPostProcessor + .identity(); + + public AuthorizeExchangeSpec() { + ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class, + ResolvableType.forClassWithGenerics(ReactiveAuthorizationManager.class, ServerWebExchange.class)); + ObjectProvider>> postProcessor = getBeanProvider( + type); + postProcessor.ifUnique((p) -> this.postProcessor = p); + } + /** * Allows method chaining to continue configuring the {@link ServerHttpSecurity} * @return the {@link ServerHttpSecurity} to continue configuring @@ -1847,10 +1871,7 @@ public class ServerHttpSecurity { Assert.state(this.matcher == null, () -> "The matcher " + this.matcher + " does not have an access rule defined"); ReactiveAuthorizationManager manager = this.managerBldr.build(); - ObservationRegistry registry = getBeanOrDefault(ObservationRegistry.class, ObservationRegistry.NOOP); - if (!registry.isNoop()) { - manager = new ObservationReactiveAuthorizationManager<>(registry, manager); - } + manager = this.postProcessor.postProcess(manager); AuthorizationWebFilter result = new AuthorizationWebFilter(manager); http.addFilterAt(result, SecurityWebFiltersOrder.AUTHORIZATION); }