Quellcode durchsuchen

Make Observations Selectable

Closes gh-15678
Josh Cummings vor 11 Monaten
Ursprung
Commit
d6b620b9f7
25 geänderte Dateien mit 927 neuen und 268 gelöschten Zeilen
  1. 71 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodObservationConfiguration.java
  2. 1 2
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java
  3. 71 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodObservationConfiguration.java
  4. 2 0
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfiguration.java
  5. 1 2
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java
  6. 0 53
      config/src/main/java/org/springframework/security/config/annotation/observation/configuration/AbstractObservationObjectPostProcessor.java
  7. 0 87
      config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ObservationConfiguration.java
  8. 0 87
      config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ReactiveObservationConfiguration.java
  9. 88 0
      config/src/main/java/org/springframework/security/config/annotation/rsocket/ReactiveObservationConfiguration.java
  10. 1 2
      config/src/main/java/org/springframework/security/config/annotation/rsocket/ReactiveObservationImportSelector.java
  11. 88 0
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/ObservationConfiguration.java
  12. 1 2
      config/src/main/java/org/springframework/security/config/annotation/web/configuration/ObservationImportSelector.java
  13. 88 0
      config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveObservationConfiguration.java
  14. 1 2
      config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveObservationImportSelector.java
  15. 16 9
      config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketObservationConfiguration.java
  16. 1 2
      config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketObservationImportSelector.java
  17. 115 0
      config/src/main/java/org/springframework/security/config/observation/SecurityObservationSettings.java
  18. 49 0
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java
  19. 55 2
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java
  20. 24 0
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java
  21. 32 0
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java
  22. 34 0
      config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationTests.java
  23. 56 0
      config/src/test/java/org/springframework/security/config/observation/SecurityObservationSettingsTests.java
  24. 66 7
      docs/modules/ROOT/pages/reactive/integrations/observability.adoc
  25. 66 11
      docs/modules/ROOT/pages/servlet/integrations/observability.adoc

+ 71 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodObservationConfiguration.java

@@ -0,0 +1,71 @@
+/*
+ * 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.method.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.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.config.observation.SecurityObservationSettings;
+
+@Configuration(proxyBeanMethods = false)
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+class MethodObservationConfiguration {
+
+	private static final SecurityObservationSettings all = SecurityObservationSettings.withDefaults()
+		.shouldObserveRequests(true)
+		.shouldObserveAuthentications(true)
+		.shouldObserveAuthorizations(true)
+		.build();
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<AuthorizationManager<MethodInvocation>> methodAuthorizationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public AuthorizationManager postProcess(AuthorizationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
+				return active ? new ObservationAuthorizationManager<>(r, object) : object;
+			}
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<AuthorizationManager<MethodInvocationResult>> methodResultAuthorizationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public AuthorizationManager postProcess(AuthorizationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
+				return active ? new ObservationAuthorizationManager<>(r, object) : object;
+			}
+		};
+	}
+
+}

+ 1 - 2
config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java

@@ -68,8 +68,7 @@ final class MethodSecuritySelector implements ImportSelector {
 			imports.add(AuthorizationProxyDataConfiguration.class.getName());
 		}
 		if (isObservabilityPresent) {
-			imports.add(
-					"org.springframework.security.config.annotation.observation.configuration.ObservationConfiguration");
+			imports.add(MethodObservationConfiguration.class.getName());
 		}
 		return imports.toArray(new String[0]);
 	}

+ 71 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodObservationConfiguration.java

@@ -0,0 +1,71 @@
+/*
+ * 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.method.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.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.config.observation.SecurityObservationSettings;
+
+@Configuration(proxyBeanMethods = false)
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+class ReactiveMethodObservationConfiguration {
+
+	private static final SecurityObservationSettings all = SecurityObservationSettings.withDefaults()
+		.shouldObserveRequests(true)
+		.shouldObserveAuthentications(true)
+		.shouldObserveAuthorizations(true)
+		.build();
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocation>> methodAuthorizationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public ReactiveAuthorizationManager postProcess(ReactiveAuthorizationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
+				return active ? new ObservationReactiveAuthorizationManager<>(r, object) : object;
+			}
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocationResult>> methodResultAuthorizationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public ReactiveAuthorizationManager postProcess(ReactiveAuthorizationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
+				return active ? new ObservationReactiveAuthorizationManager<>(r, object) : object;
+			}
+		};
+	}
+
+}

+ 2 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfiguration.java

@@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Fallback;
 import org.springframework.context.annotation.ImportAware;
 import org.springframework.context.annotation.Role;
 import org.springframework.core.type.AnnotationMetadata;
@@ -82,6 +83,7 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
 
 	@Bean
 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	@Fallback
 	static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
 			ReactiveMethodSecurityConfiguration configuration) {
 		DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();

+ 1 - 2
config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java

@@ -62,8 +62,7 @@ class ReactiveMethodSecuritySelector implements ImportSelector {
 			imports.add(AuthorizationProxyDataConfiguration.class.getName());
 		}
 		if (isObservabilityPresent) {
-			imports.add(
-					"org.springframework.security.config.annotation.observation.configuration.ReactiveObservationConfiguration");
+			imports.add(ReactiveMethodObservationConfiguration.class.getName());
 		}
 		imports.add(AuthorizationProxyConfiguration.class.getName());
 		return imports.toArray(new String[0]);

+ 0 - 53
config/src/main/java/org/springframework/security/config/annotation/observation/configuration/AbstractObservationObjectPostProcessor.java

@@ -1,53 +0,0 @@
-/*
- * 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);
-	}
-
-}

+ 0 - 87
config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ObservationConfiguration.java

@@ -1,87 +0,0 @@
-/*
- * 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) {
-		};
-	}
-
-}

+ 0 - 87
config/src/main/java/org/springframework/security/config/annotation/observation/configuration/ReactiveObservationConfiguration.java

@@ -1,87 +0,0 @@
-/*
- * 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) {
-		};
-	}
-
-}

+ 88 - 0
config/src/main/java/org/springframework/security/config/annotation/rsocket/ReactiveObservationConfiguration.java

@@ -0,0 +1,88 @@
+/*
+ * 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.rsocket;
+
+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.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.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.observation.SecurityObservationSettings;
+import org.springframework.security.web.server.ObservationWebFilterChainDecorator;
+import org.springframework.security.web.server.WebFilterChainProxy.WebFilterChainDecorator;
+import org.springframework.web.server.ServerWebExchange;
+
+@Configuration(proxyBeanMethods = false)
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+class ReactiveObservationConfiguration {
+
+	private static final SecurityObservationSettings all = SecurityObservationSettings.withDefaults()
+		.shouldObserveRequests(true)
+		.shouldObserveAuthentications(true)
+		.shouldObserveAuthorizations(true)
+		.build();
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<ReactiveAuthorizationManager<ServerWebExchange>> webAuthorizationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public ReactiveAuthorizationManager postProcess(ReactiveAuthorizationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
+				return active ? new ObservationReactiveAuthorizationManager<>(r, object) : object;
+			}
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<ReactiveAuthenticationManager> authenticationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public ReactiveAuthenticationManager postProcess(ReactiveAuthenticationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthentications();
+				return active ? new ObservationReactiveAuthenticationManager(r, object) : object;
+			}
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<WebFilterChainDecorator> filterChainDecoratorPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public WebFilterChainDecorator postProcess(WebFilterChainDecorator object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveRequests();
+				return active ? new ObservationWebFilterChainDecorator(r) : object;
+			}
+		};
+	}
+
+}

+ 1 - 2
config/src/main/java/org/springframework/security/config/annotation/rsocket/ReactiveObservationImportSelector.java

@@ -44,8 +44,7 @@ class ReactiveObservationImportSelector implements ImportSelector {
 		if (!observabilityPresent) {
 			return new String[0];
 		}
-		return new String[] {
-				"org.springframework.security.config.annotation.observation.configuration.ReactiveObservationConfiguration" };
+		return new String[] { ReactiveObservationConfiguration.class.getName() };
 	}
 
 }

+ 88 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configuration/ObservationConfiguration.java

@@ -0,0 +1,88 @@
+/*
+ * 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.web.configuration;
+
+import io.micrometer.observation.ObservationRegistry;
+import jakarta.servlet.http.HttpServletRequest;
+
+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.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.observation.SecurityObservationSettings;
+import org.springframework.security.web.FilterChainProxy.FilterChainDecorator;
+import org.springframework.security.web.ObservationFilterChainDecorator;
+
+@Configuration(proxyBeanMethods = false)
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+class ObservationConfiguration {
+
+	private static final SecurityObservationSettings all = SecurityObservationSettings.withDefaults()
+		.shouldObserveRequests(true)
+		.shouldObserveAuthentications(true)
+		.shouldObserveAuthorizations(true)
+		.build();
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<AuthorizationManager<HttpServletRequest>> webAuthorizationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public AuthorizationManager postProcess(AuthorizationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
+				return active ? new ObservationAuthorizationManager<>(r, object) : object;
+			}
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<AuthenticationManager> authenticationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public AuthenticationManager postProcess(AuthenticationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthentications();
+				return active ? new ObservationAuthenticationManager(r, object) : object;
+			}
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<FilterChainDecorator> filterChainDecoratorPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public FilterChainDecorator postProcess(FilterChainDecorator object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveRequests();
+				return active ? new ObservationFilterChainDecorator(r) : object;
+			}
+		};
+	}
+
+}

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

@@ -43,8 +43,7 @@ class ObservationImportSelector implements ImportSelector {
 		if (!observabilityPresent) {
 			return new String[0];
 		}
-		return new String[] {
-				"org.springframework.security.config.annotation.observation.configuration.ObservationConfiguration" };
+		return new String[] { ObservationConfiguration.class.getName() };
 	}
 
 }

+ 88 - 0
config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveObservationConfiguration.java

@@ -0,0 +1,88 @@
+/*
+ * 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.web.reactive;
+
+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.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.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.observation.SecurityObservationSettings;
+import org.springframework.security.web.server.ObservationWebFilterChainDecorator;
+import org.springframework.security.web.server.WebFilterChainProxy.WebFilterChainDecorator;
+import org.springframework.web.server.ServerWebExchange;
+
+@Configuration(proxyBeanMethods = false)
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+class ReactiveObservationConfiguration {
+
+	private static final SecurityObservationSettings all = SecurityObservationSettings.withDefaults()
+		.shouldObserveRequests(true)
+		.shouldObserveAuthentications(true)
+		.shouldObserveAuthorizations(true)
+		.build();
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<ReactiveAuthorizationManager<ServerWebExchange>> webAuthorizationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public ReactiveAuthorizationManager postProcess(ReactiveAuthorizationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
+				return active ? new ObservationReactiveAuthorizationManager<>(r, object) : object;
+			}
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<ReactiveAuthenticationManager> authenticationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public ReactiveAuthenticationManager postProcess(ReactiveAuthenticationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthentications();
+				return active ? new ObservationReactiveAuthenticationManager(r, object) : object;
+			}
+		};
+	}
+
+	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	static ObjectPostProcessor<WebFilterChainDecorator> filterChainDecoratorPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public WebFilterChainDecorator postProcess(WebFilterChainDecorator object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveRequests();
+				return active ? new ObservationWebFilterChainDecorator(r) : object;
+			}
+		};
+	}
+
+}

+ 1 - 2
config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveObservationImportSelector.java

@@ -43,8 +43,7 @@ class ReactiveObservationImportSelector implements ImportSelector {
 		if (!observabilityPresent) {
 			return new String[0];
 		}
-		return new String[] {
-				"org.springframework.security.config.annotation.observation.configuration.ReactiveObservationConfiguration" };
+		return new String[] { ReactiveObservationConfiguration.class.getName() };
 	}
 
 }

+ 16 - 9
config/src/main/java/org/springframework/security/config/annotation/observation/configuration/WebSocketObservationConfiguration.java → config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketObservationConfiguration.java

@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.springframework.security.config.annotation.observation.configuration;
+package org.springframework.security.config.annotation.web.socket;
 
 import io.micrometer.observation.ObservationRegistry;
 
@@ -27,22 +27,29 @@ import org.springframework.messaging.Message;
 import org.springframework.security.authorization.AuthorizationManager;
 import org.springframework.security.authorization.ObservationAuthorizationManager;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.observation.SecurityObservationSettings;
 
 @Configuration(proxyBeanMethods = false)
 @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 class WebSocketObservationConfiguration {
 
-	private final ObjectProvider<ObservationRegistry> observationRegistry;
-
-	WebSocketObservationConfiguration(ObjectProvider<ObservationRegistry> observationRegistry) {
-		this.observationRegistry = observationRegistry;
-	}
+	private static final SecurityObservationSettings all = SecurityObservationSettings.withDefaults()
+		.shouldObserveRequests(true)
+		.shouldObserveAuthentications(true)
+		.shouldObserveAuthorizations(true)
+		.build();
 
 	@Bean
 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
-	ObjectPostProcessor<AuthorizationManager<Message<?>>> messageAuthorizationManagerPostProcessor() {
-		return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
-				ObservationAuthorizationManager::new) {
+	static ObjectPostProcessor<AuthorizationManager<Message<?>>> webAuthorizationManagerPostProcessor(
+			ObjectProvider<ObservationRegistry> registry, ObjectProvider<SecurityObservationSettings> predicate) {
+		return new ObjectPostProcessor<>() {
+			@Override
+			public AuthorizationManager postProcess(AuthorizationManager object) {
+				ObservationRegistry r = registry.getIfUnique(() -> ObservationRegistry.NOOP);
+				boolean active = !r.isNoop() && predicate.getIfUnique(() -> all).shouldObserveAuthorizations();
+				return active ? new ObservationAuthorizationManager<>(r, object) : object;
+			}
 		};
 	}
 

+ 1 - 2
config/src/main/java/org/springframework/security/config/annotation/web/socket/WebSocketObservationImportSelector.java

@@ -43,8 +43,7 @@ class WebSocketObservationImportSelector implements ImportSelector {
 		if (!observabilityPresent) {
 			return new String[0];
 		}
-		return new String[] {
-				"org.springframework.security.config.annotation.observation.configuration.WebSocketObservationConfiguration" };
+		return new String[] { WebSocketObservationConfiguration.class.getName() };
 	}
 
 }

+ 115 - 0
config/src/main/java/org/springframework/security/config/observation/SecurityObservationSettings.java

@@ -0,0 +1,115 @@
+/*
+ * 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.observation;
+
+import io.micrometer.observation.ObservationPredicate;
+
+/**
+ * An {@link ObservationPredicate} that can be used to change which Spring Security
+ * observations are made with Micrometer.
+ *
+ * <p>
+ * By default, web requests are not observed and authentications and authorizations are
+ * observed.
+ *
+ * @author Josh Cummings
+ * @since 6.4
+ */
+public final class SecurityObservationSettings {
+
+	private final boolean observeRequests;
+
+	private final boolean observeAuthentications;
+
+	private final boolean observeAuthorizations;
+
+	private SecurityObservationSettings(boolean observeRequests, boolean observeAuthentications,
+			boolean observeAuthorizations) {
+		this.observeRequests = observeRequests;
+		this.observeAuthentications = observeAuthentications;
+		this.observeAuthorizations = observeAuthorizations;
+	}
+
+	/**
+	 * Make no Spring Security observations
+	 * @return a {@link SecurityObservationSettings} with all exclusions turned on
+	 */
+	public static SecurityObservationSettings noObservations() {
+		return new SecurityObservationSettings(false, false, false);
+	}
+
+	/**
+	 * Begin the configuration of a {@link SecurityObservationSettings}
+	 * @return a {@link Builder} where filter chain observations are off and authn/authz
+	 * observations are on
+	 */
+	public static Builder withDefaults() {
+		return new Builder(false, true, true);
+	}
+
+	public boolean shouldObserveRequests() {
+		return this.observeRequests;
+	}
+
+	public boolean shouldObserveAuthentications() {
+		return this.observeAuthentications;
+	}
+
+	public boolean shouldObserveAuthorizations() {
+		return this.observeAuthorizations;
+	}
+
+	/**
+	 * A builder for configuring a {@link SecurityObservationSettings}
+	 */
+	public static final class Builder {
+
+		private boolean observeRequests;
+
+		private boolean observeAuthentications;
+
+		private boolean observeAuthorizations;
+
+		Builder(boolean observeRequests, boolean observeAuthentications, boolean observeAuthorizations) {
+			this.observeRequests = observeRequests;
+			this.observeAuthentications = observeAuthentications;
+			this.observeAuthorizations = observeAuthorizations;
+		}
+
+		public Builder shouldObserveRequests(boolean excludeFilters) {
+			this.observeRequests = excludeFilters;
+			return this;
+		}
+
+		public Builder shouldObserveAuthentications(boolean excludeAuthentications) {
+			this.observeAuthentications = excludeAuthentications;
+			return this;
+		}
+
+		public Builder shouldObserveAuthorizations(boolean excludeAuthorizations) {
+			this.observeAuthorizations = excludeAuthorizations;
+			return this;
+		}
+
+		public SecurityObservationSettings build() {
+			return new SecurityObservationSettings(this.observeRequests, this.observeAuthentications,
+					this.observeAuthorizations);
+		}
+
+	}
+
+}

+ 49 - 0
config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

@@ -87,6 +87,7 @@ import org.springframework.security.authorization.method.PrePostTemplateDefaults
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
 import org.springframework.security.config.core.GrantedAuthorityDefaults;
+import org.springframework.security.config.observation.SecurityObservationSettings;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.config.test.SpringTestParentApplicationContextExecutionListener;
@@ -114,6 +115,7 @@ import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 
 /**
  * Tests for {@link PrePostMethodSecurityConfiguration}.
@@ -1062,6 +1064,43 @@ public class PrePostMethodSecurityConfigurationTests {
 		verify(handler).onError(any());
 	}
 
+	@Test
+	@WithMockUser
+	public void prePostMethodWhenExcludeAuthorizationObservationsThenUnobserved() {
+		this.spring
+			.register(MethodSecurityServiceEnabledConfig.class, ObservationRegistryConfig.class,
+					SelectableObservationsConfig.class)
+			.autowire();
+		this.methodSecurityService.preAuthorizePermitAll();
+		ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
+		assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorize);
+		verifyNoInteractions(handler);
+	}
+
+	@Test
+	@WithMockUser
+	public void securedMethodWhenExcludeAuthorizationObservationsThenUnobserved() {
+		this.spring
+			.register(MethodSecurityServiceEnabledConfig.class, ObservationRegistryConfig.class,
+					SelectableObservationsConfig.class)
+			.autowire();
+		this.methodSecurityService.securedUser();
+		ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
+		verifyNoInteractions(handler);
+	}
+
+	@Test
+	@WithMockUser
+	public void jsr250MethodWhenExcludeAuthorizationObservationsThenUnobserved() {
+		this.spring
+			.register(MethodSecurityServiceEnabledConfig.class, ObservationRegistryConfig.class,
+					SelectableObservationsConfig.class)
+			.autowire();
+		this.methodSecurityService.jsr250RolesAllowedUser();
+		ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
+		verifyNoInteractions(handler);
+	}
+
 	private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
 		return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
 	}
@@ -1742,4 +1781,14 @@ public class PrePostMethodSecurityConfigurationTests {
 
 	}
 
+	@Configuration
+	static class SelectableObservationsConfig {
+
+		@Bean
+		SecurityObservationSettings observabilityDefaults() {
+			return SecurityObservationSettings.withDefaults().shouldObserveAuthorizations(false).build();
+		}
+
+	}
+
 }

+ 55 - 2
config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java

@@ -33,9 +33,11 @@ import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import reactor.test.StepVerifier;
 
+import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Role;
@@ -55,6 +57,7 @@ import org.springframework.security.authorization.method.AuthorizeReturnObject;
 import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.core.GrantedAuthorityDefaults;
+import org.springframework.security.config.observation.SecurityObservationSettings;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.core.Authentication;
@@ -69,6 +72,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 
 /**
  * @author Tadaya Tsuyukubo
@@ -260,6 +264,27 @@ public class ReactiveMethodSecurityConfigurationTests {
 		verify(handler).onError(any());
 	}
 
+	@Test
+	@WithMockUser
+	public void prePostMethodWhenExcludeAuthorizationObservationsThenUnobserved() {
+		this.spring
+			.register(MethodSecurityServiceConfig.class, ObservationRegistryConfig.class,
+					SelectableObservationsConfig.class)
+			.autowire();
+		ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
+		Authentication user = TestAuthentication.authenticatedUser();
+		StepVerifier
+			.create(service.preAuthorizeUser().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user)))
+			.expectNextCount(1)
+			.verifyComplete();
+		ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
+		StepVerifier
+			.create(service.preAuthorizeAdmin().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user)))
+			.expectError()
+			.verify();
+		verifyNoInteractions(handler);
+	}
+
 	private static Consumer<User.UserBuilder> authorities(String... authorities) {
 		return (builder) -> builder.authorities(authorities);
 	}
@@ -432,9 +457,37 @@ public class ReactiveMethodSecurityConfigurationTests {
 		}
 
 		@Bean
-		PrePostMethodSecurityConfigurationTests.ObservationRegistryPostProcessor observationRegistryPostProcessor(
+		ObservationRegistryPostProcessor observationRegistryPostProcessor(
 				ObjectProvider<ObservationHandler<Observation.Context>> handler) {
-			return new PrePostMethodSecurityConfigurationTests.ObservationRegistryPostProcessor(handler);
+			return new ObservationRegistryPostProcessor(handler);
+		}
+
+	}
+
+	static class ObservationRegistryPostProcessor implements BeanPostProcessor {
+
+		private final ObjectProvider<ObservationHandler<Observation.Context>> handler;
+
+		ObservationRegistryPostProcessor(ObjectProvider<ObservationHandler<Observation.Context>> handler) {
+			this.handler = handler;
+		}
+
+		@Override
+		public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+			if (bean instanceof ObservationRegistry registry) {
+				registry.observationConfig().observationHandler(this.handler.getObject());
+			}
+			return bean;
+		}
+
+	}
+
+	@Configuration
+	static class SelectableObservationsConfig {
+
+		@Bean
+		SecurityObservationSettings observabilityDefaults() {
+			return SecurityObservationSettings.withDefaults().shouldObserveAuthorizations(false).build();
 		}
 
 	}

+ 24 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

@@ -47,6 +47,7 @@ import org.springframework.security.config.annotation.web.AbstractRequestMatcher
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.core.GrantedAuthorityDefaults;
+import org.springframework.security.config.observation.SecurityObservationSettings;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.core.Authentication;
@@ -80,6 +81,7 @@ import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.springframework.security.config.Customizer.withDefaults;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@@ -650,6 +652,18 @@ public class AuthorizeHttpRequestsConfigurerTests {
 		verify(handler).onError(any());
 	}
 
+	@Test
+	public void getWhenExcludeAuthorizationObservationsThenUnobserved() throws Exception {
+		this.spring
+			.register(RoleUserConfig.class, BasicController.class, ObservationRegistryConfig.class,
+					SelectableObservationsConfig.class)
+			.autowire();
+		ObservationHandler<Observation.Context> handler = this.spring.getContext().getBean(ObservationHandler.class);
+		this.mvc.perform(get("/").with(user("user").roles("USER"))).andExpect(status().isOk());
+		this.mvc.perform(get("/").with(user("user").roles("WRONG"))).andExpect(status().isForbidden());
+		verifyNoInteractions(handler);
+	}
+
 	@Configuration
 	@EnableWebSecurity
 	static class GrantedAuthorityDefaultHasRoleConfig {
@@ -1288,4 +1302,14 @@ public class AuthorizeHttpRequestsConfigurerTests {
 
 	}
 
+	@Configuration
+	static class SelectableObservationsConfig {
+
+		@Bean
+		SecurityObservationSettings observabilityDefaults() {
+			return SecurityObservationSettings.withDefaults().shouldObserveAuthorizations(false).build();
+		}
+
+	}
+
 }

+ 32 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java

@@ -39,6 +39,7 @@ import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.observation.SecurityObservationSettings;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.core.AuthenticationException;
@@ -63,6 +64,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.springframework.security.config.Customizer.withDefaults;
@@ -189,6 +191,26 @@ public class HttpBasicConfigurerTests {
 		assertThat(context.getValue()).isInstanceOf(AuthenticationObservationContext.class);
 	}
 
+	@Test
+	public void httpBasicWhenExcludeAuthenticationObservationsThenUnobserved() throws Exception {
+		this.spring
+			.register(HttpBasic.class, Users.class, Home.class, ObservationRegistryConfig.class,
+					SelectableObservationsConfig.class)
+			.autowire();
+		ObservationHandler<Observation.Context> handler = this.spring.getContext().getBean(ObservationHandler.class);
+		this.mvc.perform(get("/").with(httpBasic("user", "password")))
+			.andExpect(status().isOk())
+			.andExpect(content().string("user"));
+		ArgumentCaptor<Observation.Context> context = ArgumentCaptor.forClass(Observation.Context.class);
+		verify(handler, atLeastOnce()).onStart(context.capture());
+		assertThat(context.getAllValues()).noneMatch((c) -> c instanceof AuthenticationObservationContext);
+		context = ArgumentCaptor.forClass(Observation.Context.class);
+		verify(handler, atLeastOnce()).onStop(context.capture());
+		assertThat(context.getAllValues()).noneMatch((c) -> c instanceof AuthenticationObservationContext);
+		this.mvc.perform(get("/").with(httpBasic("user", "wrong"))).andExpect(status().isUnauthorized());
+		verify(handler, never()).onError(any());
+	}
+
 	@Configuration
 	@EnableWebSecurity
 	static class ObjectPostProcessorConfig {
@@ -455,4 +477,14 @@ public class HttpBasicConfigurerTests {
 
 	}
 
+	@Configuration
+	static class SelectableObservationsConfig {
+
+		@Bean
+		SecurityObservationSettings observabilityDefaults() {
+			return SecurityObservationSettings.withDefaults().shouldObserveAuthentications(false).build();
+		}
+
+	}
+
 }

+ 34 - 0
config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationTests.java

@@ -69,6 +69,7 @@ import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationManager;
 import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
 import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
+import org.springframework.security.config.observation.SecurityObservationSettings;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -106,6 +107,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.springframework.security.web.csrf.CsrfTokenAssert.assertThatCsrfToken;
 
 public class WebSocketMessageBrokerSecurityConfigurationTests {
@@ -414,6 +416,28 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
 		verify(observationHandler).onError(any());
 	}
 
+	@Test
+	public void sendMessageWhenExcludeAuthorizationObservationsThenUnobserved() {
+		loadConfig(WebSocketSecurityConfig.class, ObservationRegistryConfig.class, SelectableObservationsConfig.class);
+		SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
+		headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE);
+		Message<?> message = message(headers, "/authenticated");
+		headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
+		clientInboundChannel().send(message);
+		ObservationHandler<Observation.Context> observationHandler = this.context.getBean(ObservationHandler.class);
+		headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
+		headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE);
+		message = message(headers, "/denyAll");
+		headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
+		try {
+			clientInboundChannel().send(message);
+		}
+		catch (MessageDeliveryException ex) {
+			// okay
+		}
+		verifyNoInteractions(observationHandler);
+	}
+
 	private void assertHandshake(HttpServletRequest request) {
 		TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class);
 		assertThatCsrfToken(handshakeHandler.attributes.get(CsrfToken.class.getName())).isEqualTo(this.token);
@@ -968,4 +992,14 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
 
 	}
 
+	@Configuration
+	static class SelectableObservationsConfig {
+
+		@Bean
+		SecurityObservationSettings observabilityDefaults() {
+			return SecurityObservationSettings.withDefaults().shouldObserveAuthorizations(false).build();
+		}
+
+	}
+
 }

+ 56 - 0
config/src/test/java/org/springframework/security/config/observation/SecurityObservationSettingsTests.java

@@ -0,0 +1,56 @@
+/*
+ * 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.observation;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link SecurityObservationSettings}
+ */
+public class SecurityObservationSettingsTests {
+
+	@Test
+	void withDefaultsThenFilterOffAuthenticationOnAuthorizationOn() {
+		SecurityObservationSettings defaults = SecurityObservationSettings.withDefaults().build();
+		assertThat(defaults.shouldObserveRequests()).isFalse();
+		assertThat(defaults.shouldObserveAuthentications()).isTrue();
+		assertThat(defaults.shouldObserveAuthorizations()).isTrue();
+	}
+
+	@Test
+	void noObservationsWhenConstructedThenAllOff() {
+		SecurityObservationSettings defaults = SecurityObservationSettings.noObservations();
+		assertThat(defaults.shouldObserveRequests()).isFalse();
+		assertThat(defaults.shouldObserveAuthentications()).isFalse();
+		assertThat(defaults.shouldObserveAuthorizations()).isFalse();
+	}
+
+	@Test
+	void withDefaultsWhenExclusionsThenInstanceReflects() {
+		SecurityObservationSettings defaults = SecurityObservationSettings.withDefaults()
+			.shouldObserveAuthentications(false)
+			.shouldObserveAuthorizations(false)
+			.shouldObserveRequests(true)
+			.build();
+		assertThat(defaults.shouldObserveRequests()).isTrue();
+		assertThat(defaults.shouldObserveAuthentications()).isFalse();
+		assertThat(defaults.shouldObserveAuthorizations()).isFalse();
+	}
+
+}

+ 66 - 7
docs/modules/ROOT/pages/reactive/integrations/observability.adoc

@@ -187,7 +187,7 @@ Xml::
 If you don't want any Spring Security observations, in a Spring Boot application you can publish a `ObservationRegistry.NOOP` `@Bean`.
 However, this may turn off observations for more than just Spring Security.
 
-Instead, you can alter the provided `ObservationRegistry` with an `ObservationPredicate` like the following:
+Instead, you can publish a `SecurityObservationSettings` like the following:
 
 [tabs]
 ======
@@ -196,9 +196,8 @@ Java::
 [source,java,role="primary"]
 ----
 @Bean
-ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations() {
-	ObservationPredicate predicate = (name, context) -> !name.startsWith("spring.security.");
-	return (registry) -> registry.observationConfig().observationPredicate(predicate);
+SecurityObservationSettings noSpringSecurityObservations() {
+	return SecurityObservationSettings.noObservations();
 }
 ----
 
@@ -207,17 +206,77 @@ Kotlin::
 [source,kotlin,role="secondary"]
 ----
 @Bean
-fun noSpringSecurityObservations(): ObservationRegistryCustomizer<ObservationRegistry> {
-	ObservationPredicate predicate = (name: String, context: Observation.Context) -> !name.startsWith("spring.security.")
-	(registry: ObservationRegistry) -> registry.observationConfig().observationPredicate(predicate)
+fun noSpringSecurityObservations(): SecurityObservationSettings {
+	return SecurityObservationSettings.noObservations()
 }
 ----
 ======
 
+and then Spring Security will not wrap any filter chains, authentications, or authorizations in their `ObservationXXX` counterparts.
+
 [TIP]
 There is no facility for disabling observations with XML support.
 Instead, simply do not set the `observation-registry-ref` attribute.
 
+You can also disable security for only a subset of Security's observations.
+For example, the `SecurityObservationSettings` bean excludes the filter chain observations by default.
+So, you can also do:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+SecurityObservationSettings defaultSpringSecurityObservations() {
+	return SecurityObservationSettings.withDefaults().build();
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun defaultSpringSecurityObservations(): SecurityObservationSettings {
+	return SecurityObservationSettings.withDefaults().build()
+}
+----
+======
+
+Or you can turn on and off observations individually, based on the defaults:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+SecurityObservationSettings allSpringSecurityObservations() {
+	return SecurityObservationSettings.withDefaults()
+            .shouldObserveFilterChains(true).build();
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun allSpringSecurityObservations(): SecurityObservationSettings {
+    return SecurityObservabilityDefaults.builder()
+            .shouldObserveFilterChains(true).build()
+}
+----
+======
+
+[NOTE]
+=====
+For backward compatibility, all Spring Security observations are made unless a `SecurityObservationSettings` is published.
+=====
+
 [[webflux-observability-tracing-listing]]
 === Trace Listing
 

+ 66 - 11
docs/modules/ROOT/pages/servlet/integrations/observability.adoc

@@ -192,7 +192,7 @@ Xml::
 If you don't want any Spring Security observations, in a Spring Boot application you can publish a `ObservationRegistry.NOOP` `@Bean`.
 However, this may turn off observations for more than just Spring Security.
 
-Instead, you can alter the provided `ObservationRegistry` with an `ObservationPredicate` like the following:
+Instead, you can publish a `SecurityObservationSettings` like the following:
 
 [tabs]
 ======
@@ -201,9 +201,8 @@ Java::
 [source,java,role="primary"]
 ----
 @Bean
-ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations() {
-	ObservationPredicate predicate = (name, context) -> !name.startsWith("spring.security.");
-	return (registry) -> registry.observationConfig().observationPredicate(predicate);
+SecurityObservationSettings noSpringSecurityObservations() {
+	return SecurityObservationSettings.noObservations();
 }
 ----
 
@@ -212,21 +211,77 @@ Kotlin::
 [source,kotlin,role="secondary"]
 ----
 @Bean
-fun noSpringSecurityObservations(): ObservationRegistryCustomizer<ObservationRegistry> {
-    val predicate = ObservationPredicate { name: String, _: Observation.Context? ->
-        !name.startsWith("spring.security.")
-    }
-    return ObservationRegistryCustomizer { registry: ObservationRegistry ->
-        registry.observationConfig().observationPredicate(predicate)
-    }
+fun noSpringSecurityObservations(): SecurityObservationSettings {
+	return SecurityObservationSettings.noObservations()
 }
 ----
 ======
 
+and then Spring Security will not wrap any filter chains, authentications, or authorizations in their `ObservationXXX` counterparts.
+
 [TIP]
 There is no facility for disabling observations with XML support.
 Instead, simply do not set the `observation-registry-ref` attribute.
 
+You can also disable security for only a subset of Security's observations.
+For example, the `SecurityObservationSettings` bean excludes the filter chain observations by default.
+So, you can also do:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+SecurityObservationSettings defaultSpringSecurityObservations() {
+	return SecurityObservationSettings.withDefaults().build();
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun defaultSpringSecurityObservations(): SecurityObservationSettings {
+	return SecurityObservationSettings.withDefaults().build()
+}
+----
+======
+
+Or you can turn on and off observations individually, based on the defaults:
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+SecurityObservationSettings allSpringSecurityObservations() {
+	return SecurityObservationSettings.withDefaults()
+            .shouldObserveFilterChains(true).build();
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun allSpringSecurityObservations(): SecurityObservationSettings {
+    return SecurityObservationSettings.builder()
+            .shouldObserveFilterChains(true).build()
+}
+----
+======
+
+[NOTE]
+=====
+For backward compatibility, the all Spring Security observations are made unless a `SecurityObservationSettings` is published.
+=====
+
 [[observability-tracing-listing]]
 === Trace Listing