Make Observations Selectable

Closes gh-15678
This commit is contained in:
Josh Cummings 2024-09-23 13:00:00 -06:00
parent 69e3c248fa
commit d6b620b9f7
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
25 changed files with 927 additions and 268 deletions

View File

@ -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;
}
};
}
}

View File

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

View File

@ -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;
}
};
}
}

View File

@ -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();

View File

@ -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]);

View File

@ -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);
}
}

View File

@ -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) {
};
}
}

View File

@ -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) {
};
}
}

View File

@ -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;
}
};
}
}

View File

@ -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() };
}
}

View File

@ -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;
}
};
}
}

View File

@ -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() };
}
}

View File

@ -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;
}
};
}
}

View File

@ -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() };
}
}

View File

@ -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;
}
};
}

View File

@ -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() };
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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