Abstract ObservationRegistry Behind ObjectPostProcessor

Issue gh-15678
This commit is contained in:
Josh Cummings 2024-09-19 09:43:38 -06:00
parent 1ed20aa210
commit 69e3c248fa
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
27 changed files with 645 additions and 146 deletions

View File

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

View File

@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@ -36,9 +35,9 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.Jsr250AuthorizationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -58,8 +57,15 @@ final class Jsr250MethodSecurityConfiguration implements ImportAware, AopInfrast
private final Jsr250AuthorizationManager authorizationManager = new Jsr250AuthorizationManager();
private AuthorizationManagerBeforeMethodInterceptor methodInterceptor = AuthorizationManagerBeforeMethodInterceptor
.jsr250(this.authorizationManager);
private final AuthorizationManagerBeforeMethodInterceptor methodInterceptor;
Jsr250MethodSecurityConfiguration(
ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocation>>> postProcessors) {
ObjectPostProcessor<AuthorizationManager<MethodInvocation>> postProcessor = postProcessors
.getIfUnique(ObjectPostProcessor::identity);
AuthorizationManager<MethodInvocation> manager = postProcessor.postProcess(this.authorizationManager);
this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.jsr250(manager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ -95,16 +101,6 @@ final class Jsr250MethodSecurityConfiguration implements ImportAware, AopInfrast
this.methodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy);
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry registry) {
if (registry.isNoop()) {
return;
}
AuthorizationManager<MethodInvocation> observed = new ObservationAuthorizationManager<>(registry,
this.authorizationManager);
this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.secured(observed);
}
@Autowired(required = false)
void setEventPublisher(AuthorizationEventPublisher eventPublisher) {
this.methodInterceptor.setAuthorizationEventPublisher(eventPublisher);

View File

@ -41,6 +41,9 @@ final class MethodSecuritySelector implements ImportSelector {
private static final boolean isDataPresent = ClassUtils
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
private static final boolean isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null);
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
@Override
@ -64,6 +67,10 @@ final class MethodSecuritySelector implements ImportSelector {
if (isDataPresent) {
imports.add(AuthorizationProxyDataConfiguration.class.getName());
}
if (isObservabilityPresent) {
imports.add(
"org.springframework.security.config.annotation.observation.configuration.ObservationConfiguration");
}
return imports.toArray(new String[0]);
}

View File

@ -16,8 +16,8 @@
package org.springframework.security.config.annotation.method.configuration;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopInfrastructureBean;
@ -38,14 +38,16 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.aot.hint.PrePostAuthorizeHintsRegistrar;
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -78,21 +80,29 @@ final class PrePostMethodSecurityConfiguration implements ImportAware, Applicati
private final PreFilterAuthorizationMethodInterceptor preFilterMethodInterceptor = new PreFilterAuthorizationMethodInterceptor();
private AuthorizationManagerBeforeMethodInterceptor preAuthorizeMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(this.preAuthorizeAuthorizationManager);
private final AuthorizationManagerBeforeMethodInterceptor preAuthorizeMethodInterceptor;
private AuthorizationManagerAfterMethodInterceptor postAuthorizeMethodInterceptor = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(this.postAuthorizeAuthorizationManager);
private final AuthorizationManagerAfterMethodInterceptor postAuthorizeMethodInterceptor;
private final PostFilterAuthorizationMethodInterceptor postFilterMethodInterceptor = new PostFilterAuthorizationMethodInterceptor();
private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
{
PrePostMethodSecurityConfiguration(
ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocation>>> preAuthorizeProcessor,
ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocationResult>>> postAuthorizeProcessor) {
this.preFilterMethodInterceptor.setExpressionHandler(this.expressionHandler);
this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
this.postAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
this.postFilterMethodInterceptor.setExpressionHandler(this.expressionHandler);
AuthorizationManager<MethodInvocation> preAuthorize = preAuthorizeProcessor
.getIfUnique(ObjectPostProcessor::identity)
.postProcess(this.preAuthorizeAuthorizationManager);
this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize(preAuthorize);
AuthorizationManager<MethodInvocationResult> postAuthorize = postAuthorizeProcessor
.getIfUnique(ObjectPostProcessor::identity)
.postProcess(this.postAuthorizeAuthorizationManager);
this.postAuthorizeMethodInterceptor = AuthorizationManagerAfterMethodInterceptor.postAuthorize(postAuthorize);
}
@Override
@ -144,17 +154,6 @@ final class PrePostMethodSecurityConfiguration implements ImportAware, Applicati
this.postFilterMethodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy);
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry registry) {
if (registry.isNoop()) {
return;
}
this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(new ObservationAuthorizationManager<>(registry, this.preAuthorizeAuthorizationManager));
this.postAuthorizeMethodInterceptor = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(new ObservationAuthorizationManager<>(registry, this.postAuthorizeAuthorizationManager));
}
@Autowired(required = false)
void setAuthorizationEventPublisher(AuthorizationEventPublisher publisher) {
this.preAuthorizeMethodInterceptor.setAuthorizationEventPublisher(publisher);

View File

@ -16,8 +16,8 @@
package org.springframework.security.config.annotation.method.configuration;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.AopInfrastructureBean;
@ -34,14 +34,16 @@ import org.springframework.context.annotation.Role;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.PostAuthorizeReactiveAuthorizationManager;
import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.security.authorization.method.PreAuthorizeReactiveAuthorizationManager;
import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
@ -77,22 +79,30 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration
private PostFilterAuthorizationReactiveMethodInterceptor postFilterMethodInterceptor = new PostFilterAuthorizationReactiveMethodInterceptor();
private AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeMethodInterceptor;
private final AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeMethodInterceptor;
private AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeMethodInterceptor;
private final AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeMethodInterceptor;
@Autowired(required = false)
ReactiveAuthorizationManagerMethodSecurityConfiguration(MethodSecurityExpressionHandler expressionHandler) {
ReactiveAuthorizationManagerMethodSecurityConfiguration(MethodSecurityExpressionHandler expressionHandler,
ObjectProvider<ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocation>>> preAuthorizePostProcessor,
ObjectProvider<ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocationResult>>> postAuthorizePostProcessor) {
if (expressionHandler != null) {
this.preFilterMethodInterceptor = new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
this.preAuthorizeAuthorizationManager = new PreAuthorizeReactiveAuthorizationManager(expressionHandler);
this.postFilterMethodInterceptor = new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
this.postAuthorizeAuthorizationManager = new PostAuthorizeReactiveAuthorizationManager(expressionHandler);
}
ReactiveAuthorizationManager<MethodInvocation> preAuthorize = preAuthorizePostProcessor
.getIfUnique(ObjectPostProcessor::identity)
.postProcess(this.preAuthorizeAuthorizationManager);
this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeReactiveMethodInterceptor
.preAuthorize(this.preAuthorizeAuthorizationManager);
.preAuthorize(preAuthorize);
ReactiveAuthorizationManager<MethodInvocationResult> postAuthorize = postAuthorizePostProcessor
.getIfAvailable(ObjectPostProcessor::identity)
.postProcess(this.postAuthorizeAuthorizationManager);
this.postAuthorizeMethodInterceptor = AuthorizationManagerAfterReactiveMethodInterceptor
.postAuthorize(this.postAuthorizeAuthorizationManager);
.postAuthorize(postAuthorize);
}
@Override
@ -117,17 +127,6 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration
this.postFilterMethodInterceptor.setTemplateDefaults(templateDefaults);
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry registry) {
if (registry.isNoop()) {
return;
}
this.preAuthorizeMethodInterceptor = AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(
new ObservationReactiveAuthorizationManager<>(registry, this.preAuthorizeAuthorizationManager));
this.postAuthorizeMethodInterceptor = AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(
new ObservationReactiveAuthorizationManager<>(registry, this.postAuthorizeAuthorizationManager));
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static MethodInterceptor preFilterAuthorizationMethodInterceptor(

View File

@ -38,6 +38,9 @@ class ReactiveMethodSecuritySelector implements ImportSelector {
private static final boolean isDataPresent = ClassUtils
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
private static final boolean isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null);
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
@Override
@ -58,6 +61,10 @@ class ReactiveMethodSecuritySelector implements ImportSelector {
if (isDataPresent) {
imports.add(AuthorizationProxyDataConfiguration.class.getName());
}
if (isObservabilityPresent) {
imports.add(
"org.springframework.security.config.annotation.observation.configuration.ReactiveObservationConfiguration");
}
imports.add(AuthorizationProxyConfiguration.class.getName());
return imports.toArray(new String[0]);
}

View File

@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.method.configuration;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@ -37,9 +36,9 @@ import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.SecuredAuthorizationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
/**
@ -58,8 +57,15 @@ final class SecuredMethodSecurityConfiguration implements ImportAware, AopInfras
private final SecuredAuthorizationManager authorizationManager = new SecuredAuthorizationManager();
private AuthorizationManagerBeforeMethodInterceptor methodInterceptor = AuthorizationManagerBeforeMethodInterceptor
.secured(this.authorizationManager);
private final AuthorizationManagerBeforeMethodInterceptor methodInterceptor;
SecuredMethodSecurityConfiguration(
ObjectProvider<ObjectPostProcessor<AuthorizationManager<MethodInvocation>>> postProcessors) {
ObjectPostProcessor<AuthorizationManager<MethodInvocation>> postProcessor = postProcessors
.getIfUnique(ObjectPostProcessor::identity);
AuthorizationManager<MethodInvocation> manager = postProcessor.postProcess(this.authorizationManager);
this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.secured(manager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ -90,16 +96,6 @@ final class SecuredMethodSecurityConfiguration implements ImportAware, AopInfras
this.methodInterceptor.setSecurityContextHolderStrategy(securityContextHolderStrategy);
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry registry) {
if (registry.isNoop()) {
return;
}
AuthorizationManager<MethodInvocation> observed = new ObservationAuthorizationManager<>(registry,
this.authorizationManager);
this.methodInterceptor = AuthorizationManagerBeforeMethodInterceptor.secured(observed);
}
@Autowired(required = false)
void setEventPublisher(AuthorizationEventPublisher eventPublisher) {
this.methodInterceptor.setAuthorizationEventPublisher(eventPublisher);

View File

@ -0,0 +1,53 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.observation.configuration;
import java.util.function.BiFunction;
import java.util.function.Function;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
abstract class AbstractObservationObjectPostProcessor<O> implements ObjectPostProcessor<O> {
private final ObjectProvider<ObservationRegistry> observationRegistry;
private final BiFunction<ObservationRegistry, O, O> wrapper;
AbstractObservationObjectPostProcessor(ObjectProvider<ObservationRegistry> observationRegistry,
Function<ObservationRegistry, O> constructor) {
this(observationRegistry, (registry, object) -> constructor.apply(registry));
}
AbstractObservationObjectPostProcessor(ObjectProvider<ObservationRegistry> observationRegistry,
BiFunction<ObservationRegistry, O, O> constructor) {
this.observationRegistry = observationRegistry;
this.wrapper = constructor;
}
@Override
public <O1 extends O> O1 postProcess(O1 object) {
ObservationRegistry registry = this.observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP);
if (registry.isNoop()) {
return object;
}
return (O1) this.wrapper.apply(registry, object);
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.observation.configuration;
import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.http.HttpServletRequest;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ObservationAuthenticationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.ObservationFilterChainDecorator;
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class ObservationConfiguration {
private final ObjectProvider<ObservationRegistry> observationRegistry;
ObservationConfiguration(ObjectProvider<ObservationRegistry> observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<AuthorizationManager<MethodInvocation>> methodAuthorizationManagerPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationAuthorizationManager::new) {
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<AuthorizationManager<MethodInvocationResult>> methodResultAuthorizationManagerPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationAuthorizationManager::new) {
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<AuthorizationManager<HttpServletRequest>> webAuthorizationManagerPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationAuthorizationManager::new) {
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<AuthenticationManager> authenticationManagerPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationAuthenticationManager::new) {
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<FilterChainProxy.FilterChainDecorator> filterChainDecoratorPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationFilterChainDecorator::new) {
};
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.observation.configuration;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.authentication.ObservationReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.web.server.ObservationWebFilterChainDecorator;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.web.server.ServerWebExchange;
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class ReactiveObservationConfiguration {
private final ObjectProvider<ObservationRegistry> observationRegistry;
ReactiveObservationConfiguration(ObjectProvider<ObservationRegistry> observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocation>> methodAuthorizationManagerPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationReactiveAuthorizationManager::new) {
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<ReactiveAuthorizationManager<MethodInvocationResult>> methodResultAuthorizationManagerPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationReactiveAuthorizationManager::new) {
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<ReactiveAuthorizationManager<ServerWebExchange>> webAuthorizationManagerPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationReactiveAuthorizationManager::new) {
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<ReactiveAuthenticationManager> authenticationManagerPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationReactiveAuthenticationManager::new) {
};
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<WebFilterChainProxy.WebFilterChainDecorator> filterChainDecoratorPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationWebFilterChainDecorator::new) {
};
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.observation.configuration;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.messaging.Message;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class WebSocketObservationConfiguration {
private final ObjectProvider<ObservationRegistry> observationRegistry;
WebSocketObservationConfiguration(ObjectProvider<ObservationRegistry> observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
ObjectPostProcessor<AuthorizationManager<Message<?>>> messageAuthorizationManagerPostProcessor() {
return new AbstractObservationObjectPostProcessor<>(this.observationRegistry,
ObservationAuthorizationManager::new) {
};
}
}

View File

@ -35,7 +35,8 @@ import org.springframework.context.annotation.Import;
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({ RSocketSecurityConfiguration.class, SecuritySocketAcceptorInterceptorConfiguration.class })
@Import({ RSocketSecurityConfiguration.class, SecuritySocketAcceptorInterceptorConfiguration.class,
ReactiveObservationImportSelector.class })
public @interface EnableRSocketSecurity {
}

View File

@ -16,16 +16,14 @@
package org.springframework.security.config.annotation.rsocket;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.security.authentication.ObservationReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
@ -46,7 +44,7 @@ class RSocketSecurityConfiguration {
private PasswordEncoder passwordEncoder;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private ObjectPostProcessor<ReactiveAuthenticationManager> postProcessor = ObjectPostProcessor.identity();
@Autowired(required = false)
void setAuthenticationManager(ReactiveAuthenticationManager authenticationManager) {
@ -64,8 +62,8 @@ class RSocketSecurityConfiguration {
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
void setAuthenticationManagerPostProcessor(ObjectPostProcessor<ReactiveAuthenticationManager> postProcessor) {
this.postProcessor = postProcessor;
}
@Bean(name = RSOCKET_SECURITY_BEAN_NAME)
@ -86,10 +84,7 @@ class RSocketSecurityConfiguration {
if (this.passwordEncoder != null) {
manager.setPasswordEncoder(this.passwordEncoder);
}
if (!this.observationRegistry.isNoop()) {
return new ObservationReactiveAuthenticationManager(this.observationRegistry, manager);
}
return manager;
return this.postProcessor.postProcess(manager);
}
return null;
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.rsocket;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.util.ClassUtils;
/**
* Used by {@link EnableWebFluxSecurity} to conditionally import observation configuration
* when {@link ObservationRegistry} is present.
*
* @author Josh Cummings
* @since 6.4
*/
class ReactiveObservationImportSelector implements ImportSelector {
private static final boolean observabilityPresent;
static {
ClassLoader classLoader = ReactiveObservationImportSelector.class.getClassLoader();
observabilityPresent = ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", classLoader);
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
if (!observabilityPresent) {
return new String[0];
}
return new String[] {
"org.springframework.security.config.annotation.observation.configuration.ReactiveObservationConfiguration" };
}
}

View File

@ -21,7 +21,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@ -30,12 +29,13 @@ import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ObservationAuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
@ -3279,13 +3279,10 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
setSharedObject(AuthenticationManager.class, this.authenticationManager);
}
else {
ObservationRegistry registry = getObservationRegistry();
ObjectPostProcessor<AuthenticationManager> postProcessor = getAuthenticationManagerPostProcessor();
AuthenticationManager manager = getAuthenticationRegistry().build();
if (!registry.isNoop() && manager != null) {
setSharedObject(AuthenticationManager.class, new ObservationAuthenticationManager(registry, manager));
}
else {
setSharedObject(AuthenticationManager.class, manager);
if (manager != null) {
setSharedObject(AuthenticationManager.class, postProcessor.postProcess(manager));
}
}
}
@ -3723,8 +3720,12 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
return apply(configurer);
}
private ObservationRegistry getObservationRegistry() {
return getContext().getBeanProvider(ObservationRegistry.class).getIfUnique(() -> ObservationRegistry.NOOP);
private ObjectPostProcessor<AuthenticationManager> getAuthenticationManagerPostProcessor() {
ApplicationContext context = getContext();
ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class,
AuthenticationManager.class);
ObjectProvider<ObjectPostProcessor<AuthenticationManager>> manager = context.getBeanProvider(type);
return manager.getIfUnique(ObjectPostProcessor::identity);
}
/**

View File

@ -28,8 +28,10 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.ResolvableType;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
@ -45,8 +47,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterChainProxy.FilterChainDecorator;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.ObservationFilterChainDecorator;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer;
@ -110,6 +112,9 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private ObjectPostProcessor<FilterChainDecorator> filterChainDecoratorPostProcessor = ObjectPostProcessor
.identity();
private HttpServletRequestTransformer privilegeEvaluatorRequestTransformer;
private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
@ -403,6 +408,11 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
}
catch (NoSuchBeanDefinitionException ex) {
}
ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class,
FilterChainDecorator.class);
ObjectProvider<ObjectPostProcessor<FilterChainDecorator>> postProcessor = applicationContext
.getBeanProvider(type);
this.filterChainDecoratorPostProcessor = postProcessor.getIfUnique(ObjectPostProcessor::identity);
Class<HttpServletRequestTransformer> requestTransformerClass = HttpServletRequestTransformer.class;
this.privilegeEvaluatorRequestTransformer = applicationContext.getBeanProvider(requestTransformerClass)
.getIfUnique();
@ -413,11 +423,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
this.servletContext = servletContext;
}
FilterChainProxy.FilterChainDecorator getFilterChainDecorator() {
if (this.observationRegistry.isNoop()) {
return new FilterChainProxy.VirtualFilterChainDecorator();
}
return new ObservationFilterChainDecorator(this.observationRegistry);
FilterChainDecorator getFilterChainDecorator() {
return this.filterChainDecoratorPostProcessor.postProcess(new FilterChainProxy.VirtualFilterChainDecorator());
}
/**

View File

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

View File

@ -0,0 +1,50 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configuration;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
/**
* Used by {@link EnableWebSecurity} to conditionally import observation configuration
* when {@link ObservationRegistry} is present.
*
* @author Josh Cummings
* @since 6.4
*/
class ObservationImportSelector implements ImportSelector {
private static final boolean observabilityPresent;
static {
ClassLoader classLoader = ObservationImportSelector.class.getClassLoader();
observabilityPresent = ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", classLoader);
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
if (!observabilityPresent) {
return new String[0];
}
return new String[] {
"org.springframework.security.config.annotation.observation.configuration.ObservationConfiguration" };
}
}

View File

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

View File

@ -86,7 +86,7 @@ import org.springframework.security.config.web.server.ServerHttpSecurity;
@Target(ElementType.TYPE)
@Documented
@Import({ ServerHttpSecurityConfiguration.class, WebFluxSecurityConfiguration.class,
ReactiveOAuth2ClientImportSelector.class })
ReactiveOAuth2ClientImportSelector.class, ReactiveObservationImportSelector.class })
public @interface EnableWebFluxSecurity {
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.reactive;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
/**
* Used by {@link EnableWebFluxSecurity} to conditionally import observation configuration
* when {@link ObservationRegistry} is present.
*
* @author Josh Cummings
* @since 6.4
*/
class ReactiveObservationImportSelector implements ImportSelector {
private static final boolean observabilityPresent;
static {
ClassLoader classLoader = ReactiveObservationImportSelector.class.getClassLoader();
observabilityPresent = ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", classLoader);
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
if (!observabilityPresent) {
return new String[0];
}
return new String[] {
"org.springframework.security.config.annotation.observation.configuration.ReactiveObservationConfiguration" };
}
}

View File

@ -16,8 +16,6 @@
package org.springframework.security.config.annotation.web.reactive;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectProvider;
@ -29,10 +27,10 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.security.authentication.ObservationReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
@ -67,7 +65,7 @@ class ServerHttpSecurityConfiguration {
private ReactiveCompromisedPasswordChecker compromisedPasswordChecker;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private ObjectPostProcessor<ReactiveAuthenticationManager> postProcessor = ObjectPostProcessor.identity();
@Autowired(required = false)
private BeanFactory beanFactory;
@ -98,8 +96,8 @@ class ServerHttpSecurityConfiguration {
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
void setAuthenticationManagerPostProcessor(ObjectPostProcessor<ReactiveAuthenticationManager> postProcessor) {
this.postProcessor = postProcessor;
}
@Autowired(required = false)
@ -169,10 +167,7 @@ class ServerHttpSecurityConfiguration {
}
manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker);
if (!this.observationRegistry.isNoop()) {
return new ObservationReactiveAuthenticationManager(this.observationRegistry, manager);
}
return manager;
return this.postProcessor.postProcess(manager);
}
return null;
}

View File

@ -19,20 +19,20 @@ package org.springframework.security.config.annotation.web.reactive;
import java.util.Arrays;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor;
import org.springframework.security.web.server.ObservationWebFilterChainDecorator;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.WebFilterChainProxy.DefaultWebFilterChainDecorator;
import org.springframework.security.web.server.WebFilterChainProxy.WebFilterChainDecorator;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.result.view.AbstractView;
@ -57,7 +57,7 @@ class WebFluxSecurityConfiguration {
private List<SecurityWebFilterChain> securityWebFilterChains;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private ObjectPostProcessor<WebFilterChainDecorator> postProcessor = ObjectPostProcessor.identity();
static {
isOAuth2Present = ClassUtils.isPresent(REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME,
@ -73,17 +73,16 @@ class WebFluxSecurityConfiguration {
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
void setFilterChainPostProcessor(ObjectPostProcessor<WebFilterChainDecorator> postProcessor) {
this.postProcessor = postProcessor;
}
@Bean(SPRING_SECURITY_WEBFILTERCHAINFILTER_BEAN_NAME)
@Order(WEB_FILTER_CHAIN_FILTER_ORDER)
WebFilterChainProxy springSecurityWebFilterChainFilter() {
WebFilterChainProxy proxy = new WebFilterChainProxy(getSecurityWebFilterChains());
if (!this.observationRegistry.isNoop()) {
proxy.setFilterChainDecorator(new ObservationWebFilterChainDecorator(this.observationRegistry));
}
WebFilterChainDecorator decorator = this.postProcessor.postProcess(new DefaultWebFilterChainDecorator());
proxy.setFilterChainDecorator(decorator);
return proxy;
}

View File

@ -52,7 +52,7 @@ import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(WebSocketMessageBrokerSecurityConfiguration.class)
@Import({ WebSocketMessageBrokerSecurityConfiguration.class, WebSocketObservationImportSelector.class })
public @interface EnableWebSocketSecurity {
}

View File

@ -20,8 +20,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@ -33,8 +31,8 @@ import org.springframework.messaging.handler.invocation.HandlerMethodArgumentRes
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -79,7 +77,7 @@ final class WebSocketMessageBrokerSecurityConfiguration
private AuthorizationManager<Message<?>> authorizationManager = ANY_MESSAGE_AUTHENTICATED;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private ObjectPostProcessor<AuthorizationManager<Message<?>>> postProcessor = ObjectPostProcessor.identity();
private ApplicationContext context;
@ -106,9 +104,7 @@ final class WebSocketMessageBrokerSecurityConfiguration
}
AuthorizationManager<Message<?>> manager = this.authorizationManager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, manager);
}
manager = this.postProcessor.postProcess(manager);
AuthorizationChannelInterceptor interceptor = new AuthorizationChannelInterceptor(manager);
interceptor.setAuthorizationEventPublisher(new SpringAuthorizationEventPublisher(this.context));
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
@ -128,8 +124,9 @@ final class WebSocketMessageBrokerSecurityConfiguration
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
void setMessageAuthorizationManagerPostProcessor(
ObjectPostProcessor<AuthorizationManager<Message<?>>> postProcessor) {
this.postProcessor = postProcessor;
}
@Autowired(required = false)

View File

@ -0,0 +1,50 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.socket;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
/**
* Used by {@link EnableWebSocketSecurity} to conditionally import observation
* configuration when {@link ObservationRegistry} is present.
*
* @author Josh Cummings
* @since 6.4
*/
class WebSocketObservationImportSelector implements ImportSelector {
private static final boolean observabilityPresent;
static {
ClassLoader classLoader = WebSocketObservationImportSelector.class.getClassLoader();
observabilityPresent = ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", classLoader);
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
if (!observabilityPresent) {
return new String[0];
}
return new String[] {
"org.springframework.security.config.annotation.observation.configuration.WebSocketObservationConfiguration" };
}
}

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@ -33,13 +34,13 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
@ -55,9 +56,9 @@ import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager;
import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
@ -1740,6 +1741,18 @@ public class ServerHttpSecurity {
return this.context.getBeanProvider(beanClass).getIfUnique(() -> defaultInstance);
}
private <T> ObjectProvider<T> getBeanProvider(ResolvableType type) {
if (this.context == null) {
return new ObjectProvider<>() {
@Override
public Iterator<T> iterator() {
return Collections.emptyIterator();
}
};
}
return this.context.getBeanProvider(type);
}
private <T> T getBeanOrNull(Class<T> beanClass) {
return getBeanOrNull(ResolvableType.forClass(beanClass));
}
@ -1795,6 +1808,17 @@ public class ServerHttpSecurity {
private PathPatternParser pathPatternParser;
private ObjectPostProcessor<ReactiveAuthorizationManager<ServerWebExchange>> postProcessor = ObjectPostProcessor
.identity();
public AuthorizeExchangeSpec() {
ResolvableType type = ResolvableType.forClassWithGenerics(ObjectPostProcessor.class,
ResolvableType.forClassWithGenerics(ReactiveAuthorizationManager.class, ServerWebExchange.class));
ObjectProvider<ObjectPostProcessor<ReactiveAuthorizationManager<ServerWebExchange>>> postProcessor = getBeanProvider(
type);
postProcessor.ifUnique((p) -> this.postProcessor = p);
}
/**
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
* @return the {@link ServerHttpSecurity} to continue configuring
@ -1847,10 +1871,7 @@ public class ServerHttpSecurity {
Assert.state(this.matcher == null,
() -> "The matcher " + this.matcher + " does not have an access rule defined");
ReactiveAuthorizationManager<ServerWebExchange> manager = this.managerBldr.build();
ObservationRegistry registry = getBeanOrDefault(ObservationRegistry.class, ObservationRegistry.NOOP);
if (!registry.isNoop()) {
manager = new ObservationReactiveAuthorizationManager<>(registry, manager);
}
manager = this.postProcessor.postProcess(manager);
AuthorizationWebFilter result = new AuthorizationWebFilter(manager);
http.addFilterAt(result, SecurityWebFiltersOrder.AUTHORIZATION);
}