Browse Source

Abstract ObservationRegistry Behind ObjectPostProcessor

Issue gh-15678
Josh Cummings 11 months ago
parent
commit
69e3c248fa
27 changed files with 645 additions and 146 deletions
  1. 9 0
      config/src/main/java/org/springframework/security/config/annotation/ObjectPostProcessor.java
  2. 10 14
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MethodSecurityConfiguration.java
  3. 7 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java
  4. 17 18
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java
  5. 17 18
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java
  6. 7 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java
  7. 10 14
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/SecuredMethodSecurityConfiguration.java
  8. 53 0
      config/src/main/java/org/springframework/security/config/annotation/observation/configuration/AbstractObservationObjectPostProcessor.java
  9. 87 0
      config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ObservationConfiguration.java
  10. 87 0
      config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ReactiveObservationConfiguration.java
  11. 49 0
      config/src/main/java/org/springframework/security/config/annotation/observation/configuration/WebSocketObservationConfiguration.java
  12. 2 1
      config/src/main/java/org/springframework/security/config/annotation/rsocket/EnableRSocketSecurity.java
  13. 5 10
      config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurityConfiguration.java
  14. 51 0
      config/src/main/java/org/springframework/security/config/annotation/rsocket/ReactiveObservationImportSelector.java
  15. 11 10
      config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java
  16. 13 6
      config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java
  17. 1 1
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java
  18. 50 0
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/ObservationImportSelector.java
  19. 11 18
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java
  20. 1 1
      config/src/main/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurity.java
  21. 50 0
      config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveObservationImportSelector.java
  22. 5 10
      config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java
  23. 8 9
      config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java
  24. 1 1
      config/src/main/java/org/springframework/security/config/annotation/web/socket/EnableWebSocketSecurity.java
  25. 6 9
      config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfiguration.java
  26. 50 0
      config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketObservationImportSelector.java
  27. 27 6
      config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

+ 9 - 0
config/src/main/java/org/springframework/security/config/annotation/ObjectPostProcessor.java

@@ -31,6 +31,15 @@ import org.springframework.beans.factory.InitializingBean;
  */
 public interface ObjectPostProcessor<T> {
 
+	static <S> ObjectPostProcessor<S> identity() {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public <O extends S> O postProcess(O object) {
+				return object;
+			}
+		};
+	}
+
 	/**
 	 * Initialize the object possibly returning a modified instance that should be used
 	 * instead.

+ 10 - 14
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<ObjectPostProcessor<AuthorizationManager<MethodInvocation>>> postProcessors) {
+		ObjectPostProcessor<AuthorizationManager<MethodInvocation>> postProcessor = postProcessors
+			.getIfUnique(ObjectPostProcessor::identity);
+		AuthorizationManager<MethodInvocation> 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<MethodInvocation> observed = new ObservationAuthorizationManager<>(registry,
-				this.authorizationManager);
-		this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.secured(observed);
-	}
-
 	@Autowired(required = false)
 	void setEventPublisher(AuthorizationEventPublisher eventPublisher) {
 		this.methodInterceptor.setAuthorizationEventPublisher(eventPublisher);

+ 7 - 0
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]);
 	}
 

+ 17 - 18
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<ObjectPostProcessor<AuthorizationManager<MethodInvocation>>> preAuthorizeProcessor,
+			ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocationResult>>> postAuthorizeProcessor) {
 		this.preFilterMethodInterceptor.setExpressionHandler(this.expressionHandler);
 		this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
 		this.postAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
 		this.postFilterMethodInterceptor.setExpressionHandler(this.expressionHandler);
+		AuthorizationManager<MethodInvocation> preAuthorize = preAuthorizeProcessor
+			.getIfUnique(ObjectPostProcessor::identity)
+			.postProcess(this.preAuthorizeAuthorizationManager);
+		this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize(preAuthorize);
+		AuthorizationManager<MethodInvocationResult> 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);

+ 17 - 18
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<ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocation>>> preAuthorizePostProcessor,
+			ObjectProvider<ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocationResult>>> 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<MethodInvocation> preAuthorize = preAuthorizePostProcessor
+			.getIfUnique(ObjectPostProcessor::identity)
+			.postProcess(this.preAuthorizeAuthorizationManager);
 		this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeReactiveMethodInterceptor
-			.preAuthorize(this.preAuthorizeAuthorizationManager);
+			.preAuthorize(preAuthorize);
+		ReactiveAuthorizationManager<MethodInvocationResult> 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(

+ 7 - 0
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]);
 	}

+ 10 - 14
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<ObjectPostProcessor<AuthorizationManager<MethodInvocation>>> postProcessors) {
+		ObjectPostProcessor<AuthorizationManager<MethodInvocation>> postProcessor = postProcessors
+			.getIfUnique(ObjectPostProcessor::identity);
+		AuthorizationManager<MethodInvocation> 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<MethodInvocation> observed = new ObservationAuthorizationManager<>(registry,
-				this.authorizationManager);
-		this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.secured(observed);
-	}
-
 	@Autowired(required = false)
 	void setEventPublisher(AuthorizationEventPublisher eventPublisher) {
 		this.methodInterceptor.setAuthorizationEventPublisher(eventPublisher);

+ 53 - 0
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<O> implements ObjectPostProcessor<O> {
+
+	private final ObjectProvider<ObservationRegistry> observationRegistry;
+
+	private final BiFunction<ObservationRegistry, O, O> wrapper;
+
+	AbstractObservationObjectPostProcessor(ObjectProvider<ObservationRegistry> observationRegistry,
+			Function<ObservationRegistry, O> constructor) {
+		this(observationRegistry, (registry, object) -> constructor.apply(registry));
+	}
+
+	AbstractObservationObjectPostProcessor(ObjectProvider<ObservationRegistry> observationRegistry,
+			BiFunction<ObservationRegistry, O, O> constructor) {
+		this.observationRegistry = observationRegistry;
+		this.wrapper = constructor;
+	}
+
+	@Override
+	public <O1 extends O> O1 postProcess(O1 object) {
+		ObservationRegistry registry = this.observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP);
+		if (registry.isNoop()) {
+			return object;
+		}
+		return (O1) this.wrapper.apply(registry, object);
+	}
+
+}

+ 87 - 0
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> observationRegistry;
+
+	ObservationConfiguration(ObjectProvider<ObservationRegistry> observationRegistry) {
+		this.observationRegistry = observationRegistry;
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<AuthorizationManager<MethodInvocation>> methodAuthorizationManagerPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationAuthorizationManager::new) {
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<AuthorizationManager<MethodInvocationResult>> methodResultAuthorizationManagerPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationAuthorizationManager::new) {
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<AuthorizationManager<HttpServletRequest>> webAuthorizationManagerPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationAuthorizationManager::new) {
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<AuthenticationManager> authenticationManagerPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationAuthenticationManager::new) {
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<FilterChainProxy.FilterChainDecorator> filterChainDecoratorPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationFilterChainDecorator::new) {
+		};
+	}
+
+}

+ 87 - 0
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> observationRegistry;
+
+	ReactiveObservationConfiguration(ObjectProvider<ObservationRegistry> observationRegistry) {
+		this.observationRegistry = observationRegistry;
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocation>> methodAuthorizationManagerPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationReactiveAuthorizationManager::new) {
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocationResult>> methodResultAuthorizationManagerPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationReactiveAuthorizationManager::new) {
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<ReactiveAuthorizationManager<ServerWebExchange>> webAuthorizationManagerPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationReactiveAuthorizationManager::new) {
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<ReactiveAuthenticationManager> authenticationManagerPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationReactiveAuthenticationManager::new) {
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<WebFilterChainProxy.WebFilterChainDecorator> filterChainDecoratorPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationWebFilterChainDecorator::new) {
+		};
+	}
+
+}

+ 49 - 0
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> observationRegistry;
+
+	WebSocketObservationConfiguration(ObjectProvider<ObservationRegistry> observationRegistry) {
+		this.observationRegistry = observationRegistry;
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	ObjectPostProcessor<AuthorizationManager<Message<?>>> messageAuthorizationManagerPostProcessor() {
+		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
+				ObservationAuthorizationManager::new) {
+		};
+	}
+
+}

+ 2 - 1
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 {
 
 }

+ 5 - 10
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<ReactiveAuthenticationManager> 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<ReactiveAuthenticationManager> 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;
 	}

+ 51 - 0
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" };
+	}
+
+}

+ 11 - 10
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<Defaul
 			setSharedObject(AuthenticationManager.class, this.authenticationManager);
 		}
 		else {
-			ObservationRegistry registry = getObservationRegistry();
+			ObjectPostProcessor<AuthenticationManager> 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<Defaul
 		return apply(configurer);
 	}
 
-	private ObservationRegistry getObservationRegistry() {
-		return getContext().getBeanProvider(ObservationRegistry.class).getIfUnique(() -> ObservationRegistry.NOOP);
+	private ObjectPostProcessor<AuthenticationManager> getAuthenticationManagerPostProcessor() {
+		ApplicationContext context = getContext();
+		ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class,
+				AuthenticationManager.class);
+		ObjectProvider<ObjectPostProcessor<AuthenticationManager>> manager = context.getBeanProvider(type);
+		return manager.getIfUnique(ObjectPostProcessor::identity);
 	}
 
 	/**

+ 13 - 6
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<Filter,
 
 	private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
 
+	private ObjectPostProcessor<FilterChainDecorator> filterChainDecoratorPostProcessor = ObjectPostProcessor
+		.identity();
+
 	private HttpServletRequestTransformer privilegeEvaluatorRequestTransformer;
 
 	private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
@@ -403,6 +408,11 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
 		}
 		catch (NoSuchBeanDefinitionException ex) {
 		}
+		ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class,
+				FilterChainDecorator.class);
+		ObjectProvider<ObjectPostProcessor<FilterChainDecorator>> postProcessor = applicationContext
+			.getBeanProvider(type);
+		this.filterChainDecoratorPostProcessor = postProcessor.getIfUnique(ObjectPostProcessor::identity);
 		Class<HttpServletRequestTransformer> requestTransformerClass = HttpServletRequestTransformer.class;
 		this.privilegeEvaluatorRequestTransformer = applicationContext.getBeanProvider(requestTransformerClass)
 			.getIfUnique();
@@ -413,11 +423,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
 		this.servletContext = servletContext;
 	}
 
-	FilterChainProxy.FilterChainDecorator getFilterChainDecorator() {
-		if (this.observationRegistry.isNoop()) {
-			return new FilterChainProxy.VirtualFilterChainDecorator();
-		}
-		return new ObservationFilterChainDecorator(this.observationRegistry);
+	FilterChainDecorator getFilterChainDecorator() {
+		return this.filterChainDecoratorPostProcessor.postProcess(new FilterChainProxy.VirtualFilterChainDecorator());
 	}
 
 	/**

+ 1 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.java

@@ -82,7 +82,7 @@ import org.springframework.security.web.SecurityFilterChain;
 @Target(ElementType.TYPE)
 @Documented
 @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
-		HttpSecurityConfiguration.class })
+		HttpSecurityConfiguration.class, ObservationImportSelector.class })
 @EnableGlobalAuthentication
 public @interface EnableWebSecurity {
 

+ 50 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/ObservationImportSelector.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.configuration;
+
+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 EnableWebSecurity} to conditionally import observation configuration
+ * when {@link ObservationRegistry} is present.
+ *
+ * @author Josh Cummings
+ * @since 6.4
+ */
+class ObservationImportSelector implements ImportSelector {
+
+	private static final boolean observabilityPresent;
+
+	static {
+		ClassLoader classLoader = ObservationImportSelector.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.ObservationConfiguration" };
+	}
+
+}

+ 11 - 18
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

@@ -20,10 +20,11 @@ import java.util.List;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
-import io.micrometer.observation.ObservationRegistry;
 import jakarta.servlet.http.HttpServletRequest;
 
+import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.context.ApplicationContext;
+import org.springframework.core.ResolvableType;
 import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
 import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
 import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
@@ -32,7 +33,6 @@ import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationEventPublisher;
 import org.springframework.security.authorization.AuthorizationManager;
 import org.springframework.security.authorization.AuthorizationManagers;
-import org.springframework.security.authorization.ObservationAuthorizationManager;
 import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
@@ -68,6 +68,9 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 
 	private String rolePrefix = "ROLE_";
 
+	private ObjectPostProcessor<AuthorizationManager<HttpServletRequest>> postProcessor = ObjectPostProcessor
+		.identity();
+
 	/**
 	 * Creates an instance.
 	 * @param context the {@link ApplicationContext} to use
@@ -87,6 +90,11 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 			GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(GrantedAuthorityDefaults.class);
 			this.rolePrefix = grantedAuthorityDefaults.getRolePrefix();
 		}
+		ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class,
+				ResolvableType.forClassWithGenerics(AuthorizationManager.class, HttpServletRequest.class));
+		ObjectProvider<ObjectPostProcessor<AuthorizationManager<HttpServletRequest>>> provider = context
+			.getBeanProvider(type);
+		provider.ifUnique((postProcessor) -> this.postProcessor = postProcessor);
 	}
 
 	/**
@@ -123,17 +131,6 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 		return this.registry;
 	}
 
-	private ObservationRegistry getObservationRegistry() {
-		ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
-		String[] names = context.getBeanNamesForType(ObservationRegistry.class);
-		if (names.length == 1) {
-			return context.getBean(ObservationRegistry.class);
-		}
-		else {
-			return ObservationRegistry.NOOP;
-		}
-	}
-
 	/**
 	 * Registry for mapping a {@link RequestMatcher} to an {@link AuthorizationManager}.
 	 *
@@ -173,12 +170,8 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
 							+ ". Try completing it with something like requestUrls().<something>.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

+ 1 - 1
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 {
 
 }

+ 50 - 0
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" };
+	}
+
+}

+ 5 - 10
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<ReactiveAuthenticationManager> 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<ReactiveAuthenticationManager> 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;
 	}

+ 8 - 9
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<SecurityWebFilterChain> securityWebFilterChains;
 
-	private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
+	private ObjectPostProcessor<WebFilterChainDecorator> 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<WebFilterChainDecorator> 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;
 	}
 

+ 1 - 1
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 {
 
 }

+ 6 - 9
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<Message<?>> authorizationManager = ANY_MESSAGE_AUTHENTICATED;
 
-	private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
+	private ObjectPostProcessor<AuthorizationManager<Message<?>>> postProcessor = ObjectPostProcessor.identity();
 
 	private ApplicationContext context;
 
@@ -106,9 +104,7 @@ final class WebSocketMessageBrokerSecurityConfiguration
 		}
 
 		AuthorizationManager<Message<?>> 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<AuthorizationManager<Message<?>>> postProcessor) {
+		this.postProcessor = postProcessor;
 	}
 
 	@Autowired(required = false)

+ 50 - 0
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" };
+	}
+
+}

+ 27 - 6
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 <T> ObjectProvider<T> getBeanProvider(ResolvableType type) {
+		if (this.context == null) {
+			return new ObjectProvider<>() {
+				@Override
+				public Iterator<T> iterator() {
+					return Collections.emptyIterator();
+				}
+			};
+		}
+		return this.context.getBeanProvider(type);
+	}
+
 	private <T> T getBeanOrNull(Class<T> beanClass) {
 		return getBeanOrNull(ResolvableType.forClass(beanClass));
 	}
@@ -1795,6 +1808,17 @@ public class ServerHttpSecurity {
 
 		private PathPatternParser pathPatternParser;
 
+		private ObjectPostProcessor<ReactiveAuthorizationManager<ServerWebExchange>> postProcessor = ObjectPostProcessor
+			.identity();
+
+		public AuthorizeExchangeSpec() {
+			ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class,
+					ResolvableType.forClassWithGenerics(ReactiveAuthorizationManager.class, ServerWebExchange.class));
+			ObjectProvider<ObjectPostProcessor<ReactiveAuthorizationManager<ServerWebExchange>>> 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<ServerWebExchange> 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);
 		}