Instrument Authentication and Authorization

Closes gh-11989
Closes gh-11990
This commit is contained in:
Josh Cummings 2022-09-21 18:03:03 -06:00
parent 827384e386
commit 8c610684f3
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
30 changed files with 1643 additions and 132 deletions

View File

@ -16,12 +16,17 @@
package org.springframework.security.config.annotation.method.configuration;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.beans.factory.annotation.Autowired;
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.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.Jsr250AuthorizationManager;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
@ -40,28 +45,28 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class Jsr250MethodSecurityConfiguration {
private final Jsr250AuthorizationManager jsr250AuthorizationManager = new Jsr250AuthorizationManager();
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor jsr250AuthorizationMethodInterceptor() {
static Advisor jsr250AuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<ObservationRegistry> registryProvider) {
Jsr250AuthorizationManager jsr250 = new Jsr250AuthorizationManager();
defaultsProvider.ifAvailable((d) -> jsr250.setRolePrefix(d.getRolePrefix()));
SecurityContextHolderStrategy strategy = strategyProvider
.getIfAvailable(SecurityContextHolder::getContextHolderStrategy);
ObservationRegistry registry = registryProvider.getIfAvailable(() -> ObservationRegistry.NOOP);
AuthorizationManager<MethodInvocation> manager = manager(jsr250, registry);
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.jsr250(this.jsr250AuthorizationManager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
.jsr250(manager);
interceptor.setSecurityContextHolderStrategy(strategy);
return interceptor;
}
@Autowired(required = false)
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
this.jsr250AuthorizationManager.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
@Autowired(required = false)
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.securityContextHolderStrategy = securityContextHolderStrategy;
static <T> AuthorizationManager<T> manager(AuthorizationManager<T> jsr250, ObservationRegistry registry) {
if (registry.isNoop()) {
return jsr250;
}
return new ObservationAuthorizationManager<>(registry, jsr250);
}
}

View File

@ -16,8 +16,10 @@
package org.springframework.security.config.annotation.method.configuration;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.aop.Advisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@ -26,7 +28,8 @@ 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.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
@ -48,85 +51,80 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class PrePostMethodSecurityConfiguration {
private final PreFilterAuthorizationMethodInterceptor preFilterAuthorizationMethodInterceptor = new PreFilterAuthorizationMethodInterceptor();
private final AuthorizationManagerBeforeMethodInterceptor preAuthorizeAuthorizationMethodInterceptor;
private final PreAuthorizeAuthorizationManager preAuthorizeAuthorizationManager = new PreAuthorizeAuthorizationManager();
private final AuthorizationManagerAfterMethodInterceptor postAuthorizeAuthorizaitonMethodInterceptor;
private final PostAuthorizeAuthorizationManager postAuthorizeAuthorizationManager = new PostAuthorizeAuthorizationManager();
private final PostFilterAuthorizationMethodInterceptor postFilterAuthorizationMethodInterceptor = new PostFilterAuthorizationMethodInterceptor();
private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
@Autowired
PrePostMethodSecurityConfiguration(ApplicationContext context) {
this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
this.preAuthorizeAuthorizationMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(this.preAuthorizeAuthorizationManager);
this.postAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
this.postAuthorizeAuthorizaitonMethodInterceptor = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(this.postAuthorizeAuthorizationManager);
this.preFilterAuthorizationMethodInterceptor.setExpressionHandler(this.expressionHandler);
this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(this.expressionHandler);
this.expressionHandler.setApplicationContext(context);
AuthorizationEventPublisher publisher = new SpringAuthorizationEventPublisher(context);
this.preAuthorizeAuthorizationMethodInterceptor.setAuthorizationEventPublisher(publisher);
this.postAuthorizeAuthorizaitonMethodInterceptor.setAuthorizationEventPublisher(publisher);
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
static Advisor preFilterAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider, ApplicationContext context) {
PreFilterAuthorizationMethodInterceptor preFilter = new PreFilterAuthorizationMethodInterceptor();
strategyProvider.ifAvailable(preFilter::setSecurityContextHolderStrategy);
preFilter.setExpressionHandler(
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
return preFilter;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preFilterAuthorizationMethodInterceptor() {
return this.preFilterAuthorizationMethodInterceptor;
static Advisor preAuthorizeAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
manager.setExpressionHandler(
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(manager(manager, registryProvider));
strategyProvider.ifAvailable(preAuthorize::setSecurityContextHolderStrategy);
eventPublisherProvider.ifAvailable(preAuthorize::setAuthorizationEventPublisher);
return preAuthorize;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor preAuthorizeAuthorizationMethodInterceptor() {
return this.preAuthorizeAuthorizationMethodInterceptor;
static Advisor postAuthorizeAuthorizationMethodInterceptor(
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
manager.setExpressionHandler(
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
AuthorizationManagerAfterMethodInterceptor postAuthorize = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(manager(manager, registryProvider));
strategyProvider.ifAvailable(postAuthorize::setSecurityContextHolderStrategy);
eventPublisherProvider.ifAvailable(postAuthorize::setAuthorizationEventPublisher);
return postAuthorize;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postAuthorizeAuthorizationMethodInterceptor() {
return this.postAuthorizeAuthorizaitonMethodInterceptor;
static Advisor postFilterAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
ObjectProvider<SecurityContextHolderStrategy> strategyProvider, ApplicationContext context) {
PostFilterAuthorizationMethodInterceptor postFilter = new PostFilterAuthorizationMethodInterceptor();
strategyProvider.ifAvailable(postFilter::setSecurityContextHolderStrategy);
postFilter.setExpressionHandler(
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
return postFilter;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor postFilterAuthorizationMethodInterceptor() {
return this.postFilterAuthorizationMethodInterceptor;
private static MethodSecurityExpressionHandler defaultExpressionHandler(
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider, ApplicationContext context) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
defaultsProvider.ifAvailable((d) -> handler.setDefaultRolePrefix(d.getRolePrefix()));
handler.setApplicationContext(context);
return handler;
}
@Autowired(required = false)
void setMethodSecurityExpressionHandler(MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
this.preFilterAuthorizationMethodInterceptor.setExpressionHandler(methodSecurityExpressionHandler);
this.preAuthorizeAuthorizationManager.setExpressionHandler(methodSecurityExpressionHandler);
this.postAuthorizeAuthorizationManager.setExpressionHandler(methodSecurityExpressionHandler);
this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(methodSecurityExpressionHandler);
}
@Autowired(required = false)
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strategy) {
this.preFilterAuthorizationMethodInterceptor.setSecurityContextHolderStrategy(strategy);
this.preAuthorizeAuthorizationMethodInterceptor.setSecurityContextHolderStrategy(strategy);
this.postAuthorizeAuthorizaitonMethodInterceptor.setSecurityContextHolderStrategy(strategy);
this.postFilterAuthorizationMethodInterceptor.setSecurityContextHolderStrategy(strategy);
}
@Autowired(required = false)
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
this.expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
@Autowired(required = false)
void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) {
this.preAuthorizeAuthorizationMethodInterceptor.setAuthorizationEventPublisher(eventPublisher);
this.postAuthorizeAuthorizaitonMethodInterceptor.setAuthorizationEventPublisher(eventPublisher);
static <T> AuthorizationManager<T> manager(AuthorizationManager<T> delegate,
ObjectProvider<ObservationRegistry> registryProvider) {
ObservationRegistry registry = registryProvider.getIfAvailable(() -> ObservationRegistry.NOOP);
if (registry.isNoop()) {
return delegate;
}
return new ObservationAuthorizationManager<>(registry, delegate);
}
}

View File

@ -16,6 +16,10 @@
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.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
@ -24,8 +28,11 @@ 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;
@ -43,39 +50,39 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor(
static PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
return new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
PreAuthorizeReactiveAuthorizationManager authorizationManager = new PreAuthorizeReactiveAuthorizationManager(
expressionHandler);
static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor(
MethodSecurityExpressionHandler expressionHandler, ObjectProvider<ObservationRegistry> registryProvider) {
ReactiveAuthorizationManager<MethodInvocation> authorizationManager = manager(
new PreAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider);
return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor(
static PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
return new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
PostAuthorizeReactiveAuthorizationManager authorizationManager = new PostAuthorizeReactiveAuthorizationManager(
expressionHandler);
static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor(
MethodSecurityExpressionHandler expressionHandler, ObjectProvider<ObservationRegistry> registryProvider) {
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager = manager(
new PostAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider);
return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
@Autowired(required = false) GrantedAuthorityDefaults grantedAuthorityDefaults) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
if (grantedAuthorityDefaults != null) {
@ -84,4 +91,13 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration {
return handler;
}
static <T> ReactiveAuthorizationManager<T> manager(ReactiveAuthorizationManager<T> delegate,
ObjectProvider<ObservationRegistry> registryProvider) {
ObservationRegistry registry = registryProvider.getIfAvailable(() -> ObservationRegistry.NOOP);
if (registry.isNoop()) {
return delegate;
}
return new ObservationReactiveAuthorizationManager<>(registry, delegate);
}
}

View File

@ -16,14 +16,20 @@
package org.springframework.security.config.annotation.method.configuration;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.beans.factory.annotation.Autowired;
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.access.annotation.Secured;
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.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -39,20 +45,26 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class SecuredMethodSecurityConfiguration {
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor securedAuthorizationMethodInterceptor() {
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor.secured();
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
static Advisor securedAuthorizationMethodInterceptor(ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
ObjectProvider<ObservationRegistry> registryProvider) {
SecuredAuthorizationManager secured = new SecuredAuthorizationManager();
SecurityContextHolderStrategy strategy = strategyProvider
.getIfAvailable(SecurityContextHolder::getContextHolderStrategy);
ObservationRegistry registry = registryProvider.getIfAvailable(() -> ObservationRegistry.NOOP);
AuthorizationManager<MethodInvocation> manager = manager(secured, registry);
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.secured(manager);
interceptor.setSecurityContextHolderStrategy(strategy);
return interceptor;
}
@Autowired(required = false)
void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.securityContextHolderStrategy = securityContextHolderStrategy;
static <T> AuthorizationManager<T> manager(AuthorizationManager<T> jsr250, ObservationRegistry registry) {
if (registry.isNoop()) {
return jsr250;
}
return new ObservationAuthorizationManager<>(registry, jsr250);
}
}

View File

@ -16,11 +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.core.userdetails.ReactiveUserDetailsService;
@ -43,6 +46,8 @@ class RSocketSecurityConfiguration {
private PasswordEncoder passwordEncoder;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
@Autowired(required = false)
void setAuthenticationManager(ReactiveAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
@ -58,6 +63,11 @@ class RSocketSecurityConfiguration {
this.passwordEncoder = passwordEncoder;
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Bean(name = RSOCKET_SECURITY_BEAN_NAME)
@Scope("prototype")
RSocketSecurity rsocketSecurity(ApplicationContext context) {
@ -76,6 +86,9 @@ class RSocketSecurityConfiguration {
if (this.passwordEncoder != null) {
manager.setPasswordEncoder(this.passwordEncoder);
}
if (!this.observationRegistry.isNoop()) {
return new ObservationReactiveAuthenticationManager(this.observationRegistry, manager);
}
return manager;
}
return null;

View File

@ -21,6 +21,7 @@ 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;
@ -34,6 +35,7 @@ import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
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;
@ -2994,7 +2996,14 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
setSharedObject(AuthenticationManager.class, this.authenticationManager);
}
else {
setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
ObservationRegistry registry = getObservationRegistry();
AuthenticationManager manager = getAuthenticationRegistry().build();
if (!registry.isNoop()) {
setSharedObject(AuthenticationManager.class, new ObservationAuthenticationManager(registry, manager));
}
else {
setSharedObject(AuthenticationManager.class, manager);
}
}
}
@ -3418,6 +3427,15 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
return apply(configurer);
}
private ObservationRegistry getObservationRegistry() {
ApplicationContext context = getContext();
String[] names = context.getBeanNamesForType(ObservationRegistry.class);
if (names.length == 1) {
return (ObservationRegistry) context.getBean(names[0]);
}
return ObservationRegistry.NOOP;
}
/**
* Allows mapping HTTP requests that this {@link HttpSecurity} will be used for
*

View File

@ -18,6 +18,7 @@ package org.springframework.security.config.annotation.web.configurers;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.ApplicationContext;
@ -26,6 +27,7 @@ import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
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.config.annotation.web.AbstractRequestMatcherRegistry;
@ -102,6 +104,17 @@ 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}.
*
@ -141,7 +154,12 @@ 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())");
return postProcess(this.managerBuilder.build());
ObservationRegistry registry = getObservationRegistry();
RequestMatcherDelegatingAuthorizationManager manager = postProcess(this.managerBuilder.build());
if (registry.isNoop()) {
return manager;
}
return new ObservationAuthorizationManager<>(registry, manager);
}
@Override

View File

@ -16,6 +16,8 @@
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;
@ -27,6 +29,7 @@ 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.config.web.server.ServerHttpSecurity;
@ -60,6 +63,8 @@ class ServerHttpSecurityConfiguration {
private ReactiveUserDetailsPasswordService userDetailsPasswordService;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
@Autowired(required = false)
private BeanFactory beanFactory;
@ -88,6 +93,11 @@ class ServerHttpSecurityConfiguration {
this.userDetailsPasswordService = userDetailsPasswordService;
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Bean
static WebFluxConfigurer authenticationPrincipalArgumentResolverConfigurer(
ObjectProvider<AuthenticationPrincipalArgumentResolver> authenticationPrincipalArgumentResolver) {
@ -143,6 +153,9 @@ class ServerHttpSecurityConfiguration {
manager.setPasswordEncoder(this.passwordEncoder);
}
manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
if (!this.observationRegistry.isNoop()) {
return new ObservationReactiveAuthenticationManager(this.observationRegistry, manager);
}
return manager;
}
return null;

View File

@ -20,6 +20,8 @@ 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;
@ -31,6 +33,7 @@ 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.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -68,8 +71,9 @@ final class WebSocketMessageBrokerSecurityConfiguration
private final ChannelInterceptor csrfChannelInterceptor = new CsrfChannelInterceptor();
private AuthorizationChannelInterceptor authorizationChannelInterceptor = new AuthorizationChannelInterceptor(
ANY_MESSAGE_AUTHENTICATED);
private AuthorizationManager<Message<?>> authorizationManager = ANY_MESSAGE_AUTHENTICATED;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private ApplicationContext context;
@ -86,12 +90,15 @@ final class WebSocketMessageBrokerSecurityConfiguration
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
this.authorizationChannelInterceptor
.setAuthorizationEventPublisher(new SpringAuthorizationEventPublisher(this.context));
this.authorizationChannelInterceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
AuthorizationManager<Message<?>> manager = this.authorizationManager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, manager);
}
AuthorizationChannelInterceptor interceptor = new AuthorizationChannelInterceptor(manager);
interceptor.setAuthorizationEventPublisher(new SpringAuthorizationEventPublisher(this.context));
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
this.securityContextChannelInterceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
registration.interceptors(this.securityContextChannelInterceptor, this.csrfChannelInterceptor,
this.authorizationChannelInterceptor);
registration.interceptors(this.securityContextChannelInterceptor, this.csrfChannelInterceptor, interceptor);
}
@Autowired(required = false)
@ -102,7 +109,12 @@ final class WebSocketMessageBrokerSecurityConfiguration
@Autowired(required = false)
void setAuthorizationManager(AuthorizationManager<Message<?>> authorizationManager) {
this.authorizationChannelInterceptor = new AuthorizationChannelInterceptor(authorizationManager);
this.authorizationManager = authorizationManager;
}
@Autowired(required = false)
void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Override

View File

@ -18,6 +18,8 @@ package org.springframework.security.config.authentication;
import java.util.Arrays;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@ -25,6 +27,7 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ObservationAuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.BeanIds;
@ -43,6 +46,8 @@ public class AuthenticationManagerFactoryBean implements FactoryBean<Authenticat
private BeanFactory bf;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
public static final String MISSING_BEAN_ERROR_MESSAGE = "Did you forget to add a global <authentication-manager> element "
+ "to your configuration (with child <authentication-provider> elements)? Alternatively you can use the "
+ "authentication-manager-ref attribute on your <http> and <global-method-security> elements.";
@ -67,7 +72,11 @@ public class AuthenticationManagerFactoryBean implements FactoryBean<Authenticat
provider.setPasswordEncoder(passwordEncoder);
}
provider.afterPropertiesSet();
return new ProviderManager(Arrays.<AuthenticationProvider>asList(provider));
ProviderManager manager = new ProviderManager(Arrays.<AuthenticationProvider>asList(provider));
if (this.observationRegistry.isNoop()) {
return manager;
}
return new ObservationAuthenticationManager(this.observationRegistry, manager);
}
}
@ -86,6 +95,10 @@ public class AuthenticationManagerFactoryBean implements FactoryBean<Authenticat
this.bf = beanFactory;
}
public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
private <T> T getBeanOrNull(Class<T> type) {
try {
return this.bf.getBean(type);

View File

@ -19,10 +19,12 @@ package org.springframework.security.config.http;
import java.util.List;
import java.util.Map;
import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.http.HttpServletRequest;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
@ -34,6 +36,7 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.config.Elements;
import org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
@ -51,6 +54,8 @@ class AuthorizationFilterParser implements BeanDefinitionParser {
private static final String ATT_ACCESS_DECISION_MANAGER_REF = "access-decision-manager-ref";
private static final String ATT_OBSERVATION_REGISTRY_REF = "observation-registry-ref";
private static final String ATT_HTTP_METHOD = "method";
private static final String ATT_PATTERN = "pattern";
@ -127,9 +132,9 @@ class AuthorizationFilterParser implements BeanDefinitionParser {
matcherToExpression.put(matcher, authorizationManager.getBeanDefinition());
}
BeanDefinitionBuilder mds = BeanDefinitionBuilder
.rootBeanDefinition(RequestMatcherDelegatingAuthorizationManagerFactory.class);
mds.setFactoryMethod("createRequestMatcherDelegatingAuthorizationManager");
mds.addConstructorArgValue(matcherToExpression);
.rootBeanDefinition(RequestMatcherDelegatingAuthorizationManagerFactory.class)
.addPropertyValue("requestMatcherMap", matcherToExpression)
.addPropertyValue("observationRegistry", getObservationRegistry(element));
return context.registerWithGeneratedName(mds.getBeanDefinition());
}
@ -169,17 +174,48 @@ class AuthorizationFilterParser implements BeanDefinitionParser {
return !StringUtils.hasText(useExpressions) || "true".equals(useExpressions);
}
private static class RequestMatcherDelegatingAuthorizationManagerFactory {
private BeanMetadataElement getObservationRegistry(Element methodSecurityElmt) {
String holderStrategyRef = methodSecurityElmt.getAttribute(ATT_OBSERVATION_REGISTRY_REF);
if (StringUtils.hasText(holderStrategyRef)) {
return new RuntimeBeanReference(holderStrategyRef);
}
return BeanDefinitionBuilder.rootBeanDefinition(ObservationRegistryFactory.class).getBeanDefinition();
}
private static AuthorizationManager<HttpServletRequest> createRequestMatcherDelegatingAuthorizationManager(
Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> beans) {
public static final class RequestMatcherDelegatingAuthorizationManagerFactory
implements FactoryBean<AuthorizationManager<HttpServletRequest>> {
private Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> beans;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
@Override
public AuthorizationManager<HttpServletRequest> getObject() throws Exception {
RequestMatcherDelegatingAuthorizationManager.Builder builder = RequestMatcherDelegatingAuthorizationManager
.builder();
for (Map.Entry<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> entry : beans
for (Map.Entry<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> entry : this.beans
.entrySet()) {
builder.add(entry.getKey(), entry.getValue());
}
return builder.add(AnyRequestMatcher.INSTANCE, AuthenticatedAuthorizationManager.authenticated()).build();
AuthorizationManager<HttpServletRequest> manager = builder
.add(AnyRequestMatcher.INSTANCE, AuthenticatedAuthorizationManager.authenticated()).build();
if (!this.observationRegistry.isNoop()) {
return new ObservationAuthorizationManager<>(this.observationRegistry, manager);
}
return manager;
}
@Override
public Class<?> getObjectType() {
return AuthorizationManager.class;
}
public void setRequestMatcherMap(Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> beans) {
this.beans = beans;
}
public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
}
@ -199,4 +235,18 @@ class AuthorizationFilterParser implements BeanDefinitionParser {
}
static class ObservationRegistryFactory implements FactoryBean<ObservationRegistry> {
@Override
public ObservationRegistry getObject() throws Exception {
return ObservationRegistry.NOOP;
}
@Override
public Class<?> getObjectType() {
return ObservationRegistry.class;
}
}
}

View File

@ -20,12 +20,14 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@ -43,8 +45,11 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.OrderComparator;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.ObservationAuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.Elements;
@ -70,6 +75,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
private static final String ATT_AUTHENTICATION_MANAGER_REF = "authentication-manager-ref";
private static final String ATT_OBSERVATION_REGISTRY_REF = "observation-registry-ref";
static final String ATT_REQUEST_MATCHER_REF = "request-matcher-ref";
static final String ATT_PATH_PATTERN = "pattern";
@ -246,7 +253,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
private BeanReference createAuthenticationManager(Element element, ParserContext pc,
ManagedList<BeanReference> authenticationProviders) {
String parentMgrRef = element.getAttribute(ATT_AUTHENTICATION_MANAGER_REF);
BeanDefinitionBuilder authManager = BeanDefinitionBuilder.rootBeanDefinition(ProviderManager.class);
BeanDefinitionBuilder authManager = BeanDefinitionBuilder
.rootBeanDefinition(ChildAuthenticationManagerFactoryBean.class);
authManager.addConstructorArgValue(authenticationProviders);
if (StringUtils.hasText(parentMgrRef)) {
RuntimeBeanReference parentAuthManager = new RuntimeBeanReference(parentMgrRef);
@ -273,6 +281,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
// gh-6009
authManager.addPropertyValue("authenticationEventPublisher",
new RootBeanDefinition(DefaultAuthenticationEventPublisher.class));
authManager.addPropertyValue("observationRegistry", getObservationRegistry(element));
authManager.getRawBeanDefinition().setSource(pc.extractSource(element));
BeanDefinition authMgrBean = authManager.getBeanDefinition();
String id = pc.getReaderContext().generateBeanName(authMgrBean);
@ -368,6 +377,14 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
registry.registerBeanDefinition(requestRejectedPostProcessorName, requestRejectedBean);
}
private static BeanMetadataElement getObservationRegistry(Element methodSecurityElmt) {
String holderStrategyRef = methodSecurityElmt.getAttribute(ATT_OBSERVATION_REGISTRY_REF);
if (StringUtils.hasText(holderStrategyRef)) {
return new RuntimeBeanReference(holderStrategyRef);
}
return BeanDefinitionBuilder.rootBeanDefinition(ObservationRegistryFactory.class).getBeanDefinition();
}
static class RequestRejectedHandlerPostProcessor implements BeanDefinitionRegistryPostProcessor {
private final String beanName;
@ -434,4 +451,62 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
}
public static final class ChildAuthenticationManagerFactoryBean implements FactoryBean<AuthenticationManager> {
private final ProviderManager delegate;
private AuthenticationEventPublisher authenticationEventPublisher = new DefaultAuthenticationEventPublisher();
private boolean eraseCredentialsAfterAuthentication = true;
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
public ChildAuthenticationManagerFactoryBean(List<AuthenticationProvider> providers,
AuthenticationManager parent) {
this.delegate = new ProviderManager(providers, parent);
}
@Override
public AuthenticationManager getObject() throws Exception {
this.delegate.setAuthenticationEventPublisher(this.authenticationEventPublisher);
this.delegate.setEraseCredentialsAfterAuthentication(this.eraseCredentialsAfterAuthentication);
if (!this.observationRegistry.isNoop()) {
return new ObservationAuthenticationManager(this.observationRegistry, this.delegate);
}
return this.delegate;
}
@Override
public Class<?> getObjectType() {
return AuthenticationManager.class;
}
public void setEraseCredentialsAfterAuthentication(boolean eraseCredentialsAfterAuthentication) {
this.eraseCredentialsAfterAuthentication = eraseCredentialsAfterAuthentication;
}
public void setAuthenticationEventPublisher(AuthenticationEventPublisher authenticationEventPublisher) {
this.authenticationEventPublisher = authenticationEventPublisher;
}
public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
}
static class ObservationRegistryFactory implements FactoryBean<ObservationRegistry> {
@Override
public ObservationRegistry getObject() throws Exception {
return ObservationRegistry.NOOP;
}
@Override
public Class<?> getObjectType() {
return ObservationRegistry.class;
}
}
}

View File

@ -20,6 +20,8 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;
@ -42,14 +44,18 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.authorization.method.Jsr250AuthorizationManager;
import org.springframework.security.authorization.method.MethodExpressionAuthorizationManager;
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.SecuredAuthorizationManager;
import org.springframework.security.config.Elements;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.core.context.SecurityContextHolder;
@ -75,6 +81,8 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
private static final String ATT_AUTHORIZATION_MGR = "authorization-manager-ref";
private static final String ATT_OBSERVATION_REGISTRY_REF = "observation-registry-ref";
private static final String ATT_ACCESS = "access";
private static final String ATT_EXPRESSION = "expression";
@ -89,6 +97,7 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
pc.extractSource(element));
pc.pushContainingComponent(compositeDef);
BeanMetadataElement securityContextHolderStrategy = getSecurityContextHolderStrategy(element);
BeanMetadataElement observationRegistry = getObservationRegistry(element);
boolean prePostAnnotationsEnabled = !element.hasAttribute(ATT_USE_PREPOST)
|| "true".equals(element.getAttribute(ATT_USE_PREPOST));
boolean useAspectJ = "aspectj".equals(element.getAttribute(ATT_MODE));
@ -100,11 +109,13 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
BeanDefinitionBuilder preAuthorizeInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PreAuthorizeAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
.addPropertyValue("observationRegistry", observationRegistry);
BeanDefinitionBuilder postAuthorizeInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PostAuthorizeAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
.addPropertyValue("observationRegistry", observationRegistry);
BeanDefinitionBuilder postFilterInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(PostFilterAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
@ -137,10 +148,10 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
boolean securedEnabled = "true".equals(element.getAttribute(ATT_USE_SECURED));
if (securedEnabled) {
BeanDefinitionBuilder securedInterceptor = BeanDefinitionBuilder
.rootBeanDefinition(AuthorizationManagerBeforeMethodInterceptor.class)
.rootBeanDefinition(SecuredAuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
.setFactoryMethod("secured");
.addPropertyValue("observationRegistry", observationRegistry);
pc.getRegistry().registerBeanDefinition("securedAuthorizationMethodInterceptor",
securedInterceptor.getBeanDefinition());
}
@ -149,7 +160,8 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
BeanDefinitionBuilder jsr250Interceptor = BeanDefinitionBuilder
.rootBeanDefinition(Jsr250AuthorizationMethodInterceptor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy);
.addPropertyValue("securityContextHolderStrategy", securityContextHolderStrategy)
.addPropertyValue("observationRegistry", observationRegistry);
pc.getRegistry().registerBeanDefinition("jsr250AuthorizationMethodInterceptor",
jsr250Interceptor.getBeanDefinition());
}
@ -182,6 +194,14 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
return null;
}
private BeanMetadataElement getObservationRegistry(Element methodSecurityElmt) {
String holderStrategyRef = methodSecurityElmt.getAttribute(ATT_OBSERVATION_REGISTRY_REF);
if (StringUtils.hasText(holderStrategyRef)) {
return new RuntimeBeanReference(holderStrategyRef);
}
return BeanDefinitionBuilder.rootBeanDefinition(ObservationRegistryFactory.class).getBeanDefinition();
}
private BeanMetadataElement getSecurityContextHolderStrategy(Element methodSecurityElmt) {
String holderStrategyRef = methodSecurityElmt.getAttribute(ATT_SECURITY_CONTEXT_HOLDER_STRATEGY_REF);
if (StringUtils.hasText(holderStrategyRef)) {
@ -295,12 +315,18 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private final Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
@Override
public AuthorizationManagerBeforeMethodInterceptor getObject() {
AuthorizationManager<MethodInvocation> manager = this.manager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, this.manager);
}
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.jsr250(this.manager);
.jsr250(manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
}
@ -325,6 +351,47 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
}
public static final class SecuredAuthorizationMethodInterceptor
implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor> {
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private final SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
@Override
public AuthorizationManagerBeforeMethodInterceptor getObject() {
AuthorizationManager<MethodInvocation> manager = this.manager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, this.manager);
}
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.secured(manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
}
@Override
public Class<?> getObjectType() {
return AuthorizationManagerBeforeMethodInterceptor.class;
}
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
this.securityContextHolderStrategy = securityContextHolderStrategy;
}
public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
}
public static final class PreAuthorizeAuthorizationMethodInterceptor
@ -333,12 +400,18 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private final PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
@Override
public AuthorizationManagerBeforeMethodInterceptor getObject() {
AuthorizationManager<MethodInvocation> manager = this.manager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, this.manager);
}
AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(this.manager);
.preAuthorize(manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
}
@ -356,6 +429,10 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
this.manager.setExpressionHandler(expressionHandler);
}
public void setObservationRegistry(ObservationRegistry registry) {
this.observationRegistry = registry;
}
}
public static final class PostAuthorizeAuthorizationMethodInterceptor
@ -364,12 +441,18 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy();
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
private final PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
@Override
public AuthorizationManagerAfterMethodInterceptor getObject() {
AuthorizationManager<MethodInvocationResult> manager = this.manager;
if (!this.observationRegistry.isNoop()) {
manager = new ObservationAuthorizationManager<>(this.observationRegistry, this.manager);
}
AuthorizationManagerAfterMethodInterceptor interceptor = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(this.manager);
.postAuthorize(manager);
interceptor.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
return interceptor;
}
@ -387,6 +470,10 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
this.manager.setExpressionHandler(expressionHandler);
}
public void setObservationRegistry(ObservationRegistry registry) {
this.observationRegistry = registry;
}
}
static class SecurityContextHolderStrategyFactory implements FactoryBean<SecurityContextHolderStrategy> {
@ -403,4 +490,18 @@ public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser
}
static class ObservationRegistryFactory implements FactoryBean<ObservationRegistry> {
@Override
public ObservationRegistry getObject() throws Exception {
return ObservationRegistry.NOOP;
}
@Override
public Class<?> getObjectType() {
return ObservationRegistry.class;
}
}
}

View File

@ -31,6 +31,7 @@ import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
@ -50,6 +51,7 @@ 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.core.Authentication;
@ -1549,6 +1551,14 @@ public class ServerHttpSecurity {
return this.context.getBean(beanClass);
}
private <T> T getBeanOrDefault(Class<T> beanClass, T defaultInstance) {
T bean = getBeanOrNull(beanClass);
if (bean == null) {
return defaultInstance;
}
return bean;
}
private <T> T getBeanOrNull(Class<T> beanClass) {
return getBeanOrNull(ResolvableType.forClass(beanClass));
}
@ -1623,7 +1633,12 @@ public class ServerHttpSecurity {
protected void configure(ServerHttpSecurity http) {
Assert.state(this.matcher == null,
() -> "The matcher " + this.matcher + " does not have an access rule defined");
AuthorizationWebFilter result = new AuthorizationWebFilter(this.managerBldr.build());
ReactiveAuthorizationManager<ServerWebExchange> manager = this.managerBldr.build();
ObservationRegistry registry = getBeanOrDefault(ObservationRegistry.class, ObservationRegistry.NOOP);
if (!registry.isNoop()) {
manager = new ObservationReactiveAuthorizationManager<>(registry, manager);
}
AuthorizationWebFilter result = new AuthorizationWebFilter(manager);
http.addFilterAt(result, SecurityWebFiltersOrder.AUTHORIZATION);
}

View File

@ -222,6 +222,9 @@ method-security.attlist &=
method-security.attlist &=
## Specifies the security context holder strategy to use, by default uses a ThreadLocal-based strategy
attribute security-context-holder-strategy-ref {xsd:string}?
method-security.attlist &=
## Use this ObservationRegistry to collect metrics on various parts of the filter chain
attribute observation-registry-ref {xsd:token}?
global-method-security =
## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with the ordered list of "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. If you use and enable all four sources of method security metadata (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250 security annotations), the metadata sources will be queried in that order. In practical terms, this enables you to use XML to override method security metadata expressed in annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize etc.), @Secured and finally JSR-250.
@ -396,6 +399,9 @@ http.attlist &=
name?
http.attlist &=
authentication-manager-ref?
http.attlist &=
## Use this ObservationRegistry to collect metrics on various parts of the filter chain
attribute observation-registry-ref {xsd:token}?
access-denied-handler =
## Defines the access-denied strategy that should be used. An access denied page can be defined or a reference to an AccessDeniedHandler instance.
@ -1057,6 +1063,9 @@ authman.attlist &=
authman.attlist &=
## If set to true, the AuthenticationManger will attempt to clear any credentials data in the returned Authentication object, once the user has been authenticated.
attribute erase-credentials {xsd:boolean}?
authman.attlist &=
## Use this ObservationRegistry to collect metrics on various parts of the filter chain
attribute observation-registry-ref {xsd:token}?
authentication-provider =
## Indicates that the contained user-service should be used as an authentication source.

View File

@ -695,6 +695,12 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="observation-registry-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Use this ObservationRegistry to collect metrics on various parts of the filter chain
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="global-method-security">
<xs:annotation>
@ -1389,6 +1395,12 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="observation-registry-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Use this ObservationRegistry to collect metrics on various parts of the filter chain
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="access-denied-handler.attlist">
@ -2990,6 +3002,12 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="observation-registry-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Use this ObservationRegistry to collect metrics on various parts of the filter chain
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="ap.attlist">

View File

@ -0,0 +1,92 @@
/*
* Copyright 2002-2022 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.authentication;
import io.micrometer.observation.Observation;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An {@link Observation.Context} used during authentications
*
* @author Josh Cummings
* @since 6.0
*/
public class AuthenticationObservationContext extends Observation.Context {
private Authentication authenticationRequest;
private Class<?> authenticationManager;
private Authentication authenticationResult;
/**
* Get the {@link Authentication} request that was observed
* @return the observed {@link Authentication} request
*/
public Authentication getAuthenticationRequest() {
return this.authenticationRequest;
}
/**
* Set the {@link Authentication} request that was observed
* @param authenticationRequest the observed {@link Authentication} request
*/
public void setAuthenticationRequest(Authentication authenticationRequest) {
Assert.notNull(authenticationRequest, "authenticationRequest cannot be null");
this.authenticationRequest = authenticationRequest;
}
/**
* Get the {@link Authentication} result that was observed
*
* <p>
* Note that if authentication failed, no {@link Authentication} result can be
* observed. In that case, this returns {@code null}.
* @return any observed {@link Authentication} result, {@code null} otherwise
*/
public Authentication getAuthenticationResult() {
return this.authenticationResult;
}
/**
* Set the {@link Authentication} result that was observed
* @param authenticationResult the observed {@link Authentication} result
*/
public void setAuthenticationResult(Authentication authenticationResult) {
this.authenticationResult = authenticationResult;
}
/**
* Get the {@link AuthenticationManager} class that processed the authentication
* @return the observed {@link AuthenticationManager} class
*/
public Class<?> getAuthenticationManagerClass() {
return this.authenticationManager;
}
/**
* Set the {@link AuthenticationManager} class that processed the authentication
* @param authenticationManagerClass the observed {@link AuthenticationManager} class
*/
public void setAuthenticationManagerClass(Class<?> authenticationManagerClass) {
Assert.notNull(authenticationManagerClass, "authenticationManagerClass class cannot be null");
this.authenticationManager = authenticationManagerClass;
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright 2002-2022 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.authentication;
import io.micrometer.common.KeyValues;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import org.jetbrains.annotations.NotNull;
import org.springframework.lang.NonNull;
/**
* An {@link ObservationConvention} for translating authentications into
* {@link KeyValues}.
*
* @author Josh Cummings
* @since 6.0
*/
public final class AuthenticationObservationConvention
implements ObservationConvention<AuthenticationObservationContext> {
static final String OBSERVATION_NAME = "spring.security.authentications";
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return OBSERVATION_NAME;
}
/**
* {@inheritDoc}
*/
@NotNull
@Override
public KeyValues getLowCardinalityKeyValues(@NonNull AuthenticationObservationContext context) {
return KeyValues.of("authentication.request.type", getAuthenticationType(context))
.and("authentication.method", getAuthenticationMethod(context))
.and("authentication.result.type", getAuthenticationResult(context))
.and("authentication.failure.type", getAuthenticationFailureType(context));
}
private String getAuthenticationType(AuthenticationObservationContext context) {
if (context.getAuthenticationRequest() == null) {
return "unknown";
}
return context.getAuthenticationRequest().getClass().getSimpleName();
}
private String getAuthenticationMethod(AuthenticationObservationContext context) {
if (context.getAuthenticationManagerClass() == null) {
return "unknown";
}
return context.getAuthenticationManagerClass().getSimpleName();
}
private String getAuthenticationResult(AuthenticationObservationContext context) {
if (context.getAuthenticationResult() == null) {
return "n/a";
}
return context.getAuthenticationResult().getClass().getSimpleName();
}
private String getAuthenticationFailureType(AuthenticationObservationContext context) {
if (context.getError() == null) {
return "n/a";
}
return context.getError().getClass().getSimpleName();
}
/**
* {@inheritDoc}
*/
@Override
public boolean supportsContext(@NotNull Observation.Context context) {
return context instanceof AuthenticationObservationContext;
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2002-2022 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.authentication;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.util.Assert;
/**
* An {@link AuthenticationManager} that observes the authentication
*
* @author Josh Cummings
* @since 6.0
*/
public final class ObservationAuthenticationManager implements AuthenticationManager {
private final ObservationRegistry registry;
private final AuthenticationManager delegate;
private final AuthenticationObservationConvention convention = new AuthenticationObservationConvention();
public ObservationAuthenticationManager(ObservationRegistry registry, AuthenticationManager delegate) {
Assert.notNull(registry, "observationRegistry cannot be null");
Assert.notNull(delegate, "authenticationManager cannot be null");
this.registry = registry;
this.delegate = delegate;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
AuthenticationObservationContext context = new AuthenticationObservationContext();
context.setAuthenticationRequest(authentication);
context.setAuthenticationManagerClass(this.delegate.getClass());
return Observation.createNotStarted(this.convention, () -> context, this.registry).observe(() -> {
Authentication result = this.delegate.authenticate(authentication);
context.setAuthenticationResult(result);
return result;
});
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2002-2022 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.authentication;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import reactor.core.publisher.Mono;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
* An {@link ReactiveAuthenticationManager} that observes the authentication
*
* @author Josh Cummings
* @since 6.0
*/
public class ObservationReactiveAuthenticationManager implements ReactiveAuthenticationManager {
private final ObservationRegistry registry;
private final ReactiveAuthenticationManager delegate;
private final AuthenticationObservationConvention convention = new AuthenticationObservationConvention();
public ObservationReactiveAuthenticationManager(ObservationRegistry registry,
ReactiveAuthenticationManager delegate) {
this.registry = registry;
this.delegate = delegate;
}
@Override
public Mono<Authentication> authenticate(Authentication authentication) throws AuthenticationException {
AuthenticationObservationContext context = new AuthenticationObservationContext();
context.setAuthenticationRequest(authentication);
context.setAuthenticationManagerClass(this.delegate.getClass());
Observation observation = Observation.createNotStarted(this.convention, () -> context, this.registry).start();
return this.delegate.authenticate(authentication).doOnSuccess((result) -> {
context.setAuthenticationResult(result);
observation.stop();
}).doOnCancel(observation::stop).doOnError((t) -> {
observation.error(t);
observation.stop();
});
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2002-2022 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.authorization;
import io.micrometer.observation.Observation;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An {@link Observation.Context} used during authorizations
*
* @author Josh Cummings
* @since 6.0
*/
public class AuthorizationObservationContext<T> extends Observation.Context {
private Authentication authentication;
private final T object;
private AuthorizationDecision decision;
public AuthorizationObservationContext(T object) {
Assert.notNull(object, "object cannot be null");
this.object = object;
}
/**
* Get the observed {@link Authentication} for this authorization
*
* <p>
* Note that if the authorization did not require inspecting the
* {@link Authentication}, this will return {@code null}.
* @return any observed {@link Authentication}, {@code null} otherwise
*/
public Authentication getAuthentication() {
return this.authentication;
}
/**
* Set the observed {@link Authentication} for this authorization
* @param authentication the observed {@link Authentication}
*/
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
/**
* Get the object for which access was requested
* @return the requested object
*/
public T getObject() {
return this.object;
}
/**
* Get the observed {@link AuthorizationDecision}
* @return the observed {@link AuthorizationDecision}
*/
public AuthorizationDecision getDecision() {
return this.decision;
}
/**
* Set the observed {@link AuthorizationDecision}
* @param decision the observed {@link AuthorizationDecision}
*/
public void setDecision(AuthorizationDecision decision) {
this.decision = decision;
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright 2002-2022 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.authorization;
import io.micrometer.common.KeyValues;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
/**
* An {@link ObservationConvention} for translating authorizations into {@link KeyValues}.
*
* @author Josh Cummings
* @since 6.0
*/
public final class AuthorizationObservationConvention
implements ObservationConvention<AuthorizationObservationContext<?>> {
static final String OBSERVATION_NAME = "spring.security.authorizations";
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return OBSERVATION_NAME;
}
/**
* {@inheritDoc}
*/
@Override
public KeyValues getLowCardinalityKeyValues(AuthorizationObservationContext<?> context) {
return KeyValues.of("authentication.type", getAuthenticationType(context))
.and("object.type", getObjectType(context))
.and("authorization.decision", getAuthorizationDecision(context));
}
/**
* {@inheritDoc}
*/
@Override
public KeyValues getHighCardinalityKeyValues(AuthorizationObservationContext<?> context) {
return KeyValues.of("authentication.authorities", getAuthorities(context)).and("authorization.decision.details",
getDecisionDetails(context));
}
@Override
public boolean supportsContext(Observation.Context context) {
return context instanceof AuthorizationObservationContext<?>;
}
private String getAuthenticationType(AuthorizationObservationContext<?> context) {
if (context.getAuthentication() == null) {
return "n/a";
}
return context.getAuthentication().getClass().getSimpleName();
}
private String getObjectType(AuthorizationObservationContext<?> context) {
if (context.getObject() == null) {
return "unknown";
}
return context.getObject().getClass().getSimpleName();
}
private String getAuthorizationDecision(AuthorizationObservationContext<?> context) {
if (context.getDecision() == null) {
return "unknown";
}
return String.valueOf(context.getDecision().isGranted());
}
private String getAuthorities(AuthorizationObservationContext<?> context) {
if (context.getAuthentication() == null) {
return "n/a";
}
return String.valueOf(context.getAuthentication().getAuthorities());
}
private String getDecisionDetails(AuthorizationObservationContext<?> context) {
if (context.getDecision() == null) {
return "unknown";
}
AuthorizationDecision decision = context.getDecision();
return String.valueOf(decision);
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2002-2022 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.authorization;
import java.util.function.Supplier;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
/**
* An {@link AuthorizationManager} that observes the authorization
*
* @author Josh Cummings
* @since 6.0
*/
public final class ObservationAuthorizationManager<T> implements AuthorizationManager<T> {
private final ObservationRegistry registry;
private final AuthorizationManager<T> delegate;
private final AuthorizationObservationConvention convention = new AuthorizationObservationConvention();
public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager<T> delegate) {
this.registry = registry;
this.delegate = delegate;
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
AuthorizationObservationContext<T> context = new AuthorizationObservationContext<>(object);
Supplier<Authentication> wrapped = () -> {
context.setAuthentication(authentication.get());
return context.getAuthentication();
};
Observation observation = Observation.createNotStarted(this.convention, () -> context, this.registry).start();
try (Observation.Scope scope = observation.openScope()) {
AuthorizationDecision decision = this.delegate.check(wrapped, object);
context.setDecision(decision);
if (decision != null && !decision.isGranted()) {
observation.error(new AccessDeniedException("Access Denied"));
}
return decision;
}
catch (Throwable ex) {
observation.error(ex);
throw ex;
}
finally {
observation.stop();
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2002-2022 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.authorization;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import reactor.core.publisher.Mono;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
/**
* An {@link ReactiveAuthorizationManager} that observes the authentication
*
* @author Josh Cummings
* @since 6.0
*/
public final class ObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
private final ObservationRegistry registry;
private final ReactiveAuthorizationManager<T> delegate;
private final AuthorizationObservationConvention convention = new AuthorizationObservationConvention();
public ObservationReactiveAuthorizationManager(ObservationRegistry registry,
ReactiveAuthorizationManager<T> delegate) {
this.registry = registry;
this.delegate = delegate;
}
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, T object) {
AuthorizationObservationContext<T> context = new AuthorizationObservationContext<>(object);
Mono<Authentication> wrapped = authentication.map((auth) -> {
context.setAuthentication(auth);
return context.getAuthentication();
});
Observation observation = Observation.createNotStarted(this.convention, () -> context, this.registry).start();
return this.delegate.check(wrapped, object).doOnSuccess((decision) -> {
context.setDecision(decision);
if (decision == null || !decision.isGranted()) {
observation.error(new AccessDeniedException("Access Denied"));
}
observation.stop();
}).doOnCancel(observation::stop).doOnError((t) -> {
observation.error(t);
observation.stop();
});
}
}

View File

@ -98,6 +98,20 @@ public final class AuthorizationManagerAfterMethodInterceptor
return interceptor;
}
/**
* Creates an interceptor for the {@link PostAuthorize} annotation
* @param authorizationManager the {@link AuthorizationManager} to use
* @return the interceptor
* @since 6.0
*/
public static AuthorizationManagerAfterMethodInterceptor postAuthorize(
AuthorizationManager<MethodInvocationResult> authorizationManager) {
AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(PostAuthorize.class), authorizationManager);
interceptor.setOrder(500);
return interceptor;
}
/**
* Determine if an {@link Authentication} has access to the {@link MethodInvocation}
* using the {@link AuthorizationManager}.

View File

@ -102,6 +102,20 @@ public final class AuthorizationManagerBeforeMethodInterceptor
return interceptor;
}
/**
* Creates an interceptor for the {@link PreAuthorize} annotation
* @param authorizationManager the {@link AuthorizationManager} to use
* @return the interceptor
* @since 6.0
*/
public static AuthorizationManagerBeforeMethodInterceptor preAuthorize(
AuthorizationManager<MethodInvocation> authorizationManager) {
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager);
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder());
return interceptor;
}
/**
* Creates an interceptor for the {@link Secured} annotation
* @return the interceptor
@ -123,6 +137,20 @@ public final class AuthorizationManagerBeforeMethodInterceptor
return interceptor;
}
/**
* Creates an interceptor for the {@link Secured} annotation
* @param authorizationManager the {@link AuthorizationManager} to use
* @return the interceptor
* @since 6.0
*/
public static AuthorizationManagerBeforeMethodInterceptor secured(
AuthorizationManager<MethodInvocation> authorizationManager) {
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(Secured.class), authorizationManager);
interceptor.setOrder(AuthorizationInterceptorsOrder.SECURED.getOrder());
return interceptor;
}
/**
* Creates an interceptor for the JSR-250 annotations
* @return the interceptor
@ -144,6 +172,21 @@ public final class AuthorizationManagerBeforeMethodInterceptor
return interceptor;
}
/**
* Creates an interceptor for the JSR-250 annotations
* @param authorizationManager the {@link AuthorizationManager} to use
* @return the interceptor
* @since 6.0
*/
public static AuthorizationManagerBeforeMethodInterceptor jsr250(
AuthorizationManager<MethodInvocation> authorizationManager) {
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(RolesAllowed.class, DenyAll.class, PermitAll.class),
authorizationManager);
interceptor.setOrder(AuthorizationInterceptorsOrder.JSR250.getOrder());
return interceptor;
}
/**
* Determine if an {@link Authentication} has access to the {@link MethodInvocation}
* using the configured {@link AuthorizationManager}.

View File

@ -0,0 +1,96 @@
/*
* Copyright 2002-2022 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.authentication;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ObservationAuthenticationManager}
*/
public class ObservationAuthenticationManagerTests {
private ObservationRegistry registry;
private ObservationHandler<Observation.Context> handler;
private AuthenticationManager authenticationManager;
private ObservationAuthenticationManager tested;
private final Authentication token = new TestingAuthenticationToken("user", "pass");
private final Authentication authentication = new TestingAuthenticationToken("user", "pass", "app");
@BeforeEach
void setup() {
this.handler = mock(ObservationHandler.class);
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig().observationHandler(this.handler);
this.registry = registry;
this.authenticationManager = mock(AuthenticationManager.class);
this.tested = new ObservationAuthenticationManager(this.registry, this.authenticationManager);
}
@Test
void authenticateWhenDefaultsThenObserves() {
given(this.handler.supportsContext(any())).willReturn(true);
given(this.authenticationManager.authenticate(any())).willReturn(this.authentication);
this.tested.authenticate(this.token);
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.handler).onStart(captor.capture());
assertThat(captor.getValue().getName()).isEqualTo(AuthenticationObservationConvention.OBSERVATION_NAME);
assertThat(captor.getValue().getError()).isNull();
assertThat(captor.getValue()).isInstanceOf(AuthenticationObservationContext.class);
AuthenticationObservationContext context = (AuthenticationObservationContext) captor.getValue();
assertThat(context.getAuthenticationManagerClass()).isEqualTo(this.authenticationManager.getClass());
assertThat(context.getAuthenticationRequest()).isEqualTo(this.token);
assertThat(context.getAuthenticationResult()).isEqualTo(this.authentication);
}
@Test
void authenticationWhenErrorsThenObserves() {
given(this.handler.supportsContext(any())).willReturn(true);
given(this.authenticationManager.authenticate(any())).willThrow(BadCredentialsException.class);
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.tested.authenticate(this.token));
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.handler).onStart(captor.capture());
assertThat(captor.getValue().getName()).isEqualTo(AuthenticationObservationConvention.OBSERVATION_NAME);
assertThat(captor.getValue().getError()).isInstanceOf(AuthenticationException.class);
assertThat(captor.getValue()).isInstanceOf(AuthenticationObservationContext.class);
AuthenticationObservationContext context = (AuthenticationObservationContext) captor.getValue();
assertThat(context.getAuthenticationManagerClass()).isEqualTo(this.authenticationManager.getClass());
assertThat(context.getAuthenticationRequest()).isEqualTo(this.token);
assertThat(context.getAuthenticationResult()).isNull();
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright 2002-2022 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.authentication;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ObservationAuthenticationManager}
*/
public class ObservationReactiveAuthenticationManagerTests {
private ObservationRegistry registry;
private ObservationHandler<Observation.Context> handler;
private ReactiveAuthenticationManager authenticationManager;
private ObservationReactiveAuthenticationManager tested;
private final Authentication token = new TestingAuthenticationToken("user", "pass");
private final Authentication authentication = new TestingAuthenticationToken("user", "pass", "app");
@BeforeEach
void setup() {
this.handler = mock(ObservationHandler.class);
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig().observationHandler(this.handler);
this.registry = registry;
this.authenticationManager = mock(ReactiveAuthenticationManager.class);
this.tested = new ObservationReactiveAuthenticationManager(this.registry, this.authenticationManager);
}
@Test
void authenticateWhenDefaultsThenObserves() {
given(this.handler.supportsContext(any())).willReturn(true);
given(this.authenticationManager.authenticate(any())).willReturn(Mono.just(this.authentication));
this.tested.authenticate(this.token).block();
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.handler).onStart(captor.capture());
assertThat(captor.getValue().getName()).isEqualTo(AuthenticationObservationConvention.OBSERVATION_NAME);
assertThat(captor.getValue().getError()).isNull();
assertThat(captor.getValue()).isInstanceOf(AuthenticationObservationContext.class);
AuthenticationObservationContext context = (AuthenticationObservationContext) captor.getValue();
assertThat(context.getAuthenticationManagerClass()).isEqualTo(this.authenticationManager.getClass());
assertThat(context.getAuthenticationRequest()).isEqualTo(this.token);
assertThat(context.getAuthenticationResult()).isEqualTo(this.authentication);
}
@Test
void authenticationWhenErrorsThenObserves() {
given(this.handler.supportsContext(any())).willReturn(true);
given(this.authenticationManager.authenticate(any()))
.willReturn(Mono.error(new BadCredentialsException("fail")));
assertThatExceptionOfType(BadCredentialsException.class)
.isThrownBy(() -> this.tested.authenticate(this.token).block());
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.handler).onStart(captor.capture());
assertThat(captor.getValue().getName()).isEqualTo(AuthenticationObservationConvention.OBSERVATION_NAME);
assertThat(captor.getValue().getError()).isInstanceOf(AuthenticationException.class);
assertThat(captor.getValue()).isInstanceOf(AuthenticationObservationContext.class);
AuthenticationObservationContext context = (AuthenticationObservationContext) captor.getValue();
assertThat(context.getAuthenticationManagerClass()).isEqualTo(this.authenticationManager.getClass());
assertThat(context.getAuthenticationRequest()).isEqualTo(this.token);
assertThat(context.getAuthenticationResult()).isNull();
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2002-2022 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.authorization;
import java.util.function.Supplier;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ObservationAuthorizationManager}
*/
public class ObservationAuthorizationManagerTests {
private ObservationRegistry registry;
private ObservationHandler<Observation.Context> handler;
private AuthorizationManager<Object> authorizationManager;
private ObservationAuthorizationManager<Object> tested;
private final Supplier<Authentication> token = () -> new TestingAuthenticationToken("user", "pass");
private final Object object = new Object();
private final AuthorizationDecision grant = new AuthorizationDecision(true);
private final AuthorizationDecision deny = new AuthorizationDecision(false);
@BeforeEach
void setup() {
this.handler = mock(ObservationHandler.class);
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig().observationHandler(this.handler);
this.registry = registry;
this.authorizationManager = mock(AuthorizationManager.class);
this.tested = new ObservationAuthorizationManager<>(this.registry, this.authorizationManager);
}
@Test
void verifyWhenDefaultsThenObserves() {
given(this.handler.supportsContext(any())).willReturn(true);
given(this.authorizationManager.check(any(), any())).willReturn(this.grant);
this.tested.verify(this.token, this.object);
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.handler).onStart(captor.capture());
assertThat(captor.getValue().getName()).isEqualTo(AuthorizationObservationConvention.OBSERVATION_NAME);
assertThat(captor.getValue().getError()).isNull();
assertThat(captor.getValue()).isInstanceOf(AuthorizationObservationContext.class);
AuthorizationObservationContext<?> context = (AuthorizationObservationContext<?>) captor.getValue();
assertThat(context.getAuthentication()).isNull();
assertThat(context.getObject()).isEqualTo(this.object);
assertThat(context.getDecision()).isEqualTo(this.grant);
}
@Test
void verifyWhenErrorsThenObserves() {
given(this.handler.supportsContext(any())).willReturn(true);
given(this.authorizationManager.check(any(), any())).willReturn(this.deny);
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> this.tested.verify(this.token, this.object));
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.handler).onStart(captor.capture());
assertThat(captor.getValue().getName()).isEqualTo(AuthorizationObservationConvention.OBSERVATION_NAME);
assertThat(captor.getValue().getError()).isInstanceOf(AccessDeniedException.class);
assertThat(captor.getValue()).isInstanceOf(AuthorizationObservationContext.class);
AuthorizationObservationContext<?> context = (AuthorizationObservationContext<?>) captor.getValue();
assertThat(context.getAuthentication()).isNull();
assertThat(context.getObject()).isEqualTo(this.object);
assertThat(context.getDecision()).isEqualTo(this.deny);
}
@Test
void verifyWhenLooksUpAuthenticationThenObserves() {
given(this.handler.supportsContext(any())).willReturn(true);
given(this.authorizationManager.check(any(), any())).willAnswer((invocation) -> {
((Supplier<Authentication>) invocation.getArgument(0)).get();
return this.grant;
});
this.tested.verify(this.token, this.object);
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.handler).onStart(captor.capture());
assertThat(captor.getValue().getName()).isEqualTo(AuthorizationObservationConvention.OBSERVATION_NAME);
assertThat(captor.getValue().getError()).isNull();
AuthorizationObservationContext<?> context = (AuthorizationObservationContext<?>) captor.getValue();
assertThat(context.getAuthentication()).isEqualTo(this.token.get());
assertThat(context.getObject()).isEqualTo(this.object);
assertThat(context.getDecision()).isEqualTo(this.grant);
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2002-2022 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.authorization;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ObservationAuthorizationManager}
*/
public class ObservationReactiveAuthorizationManagerTests {
private ObservationRegistry registry;
private ObservationHandler<Observation.Context> handler;
private ReactiveAuthorizationManager<Object> authorizationManager;
private ObservationReactiveAuthorizationManager<Object> tested;
private final Mono<Authentication> token = Mono.just(new TestingAuthenticationToken("user", "pass"));
private final Object object = new Object();
private final AuthorizationDecision grant = new AuthorizationDecision(true);
private final AuthorizationDecision deny = new AuthorizationDecision(false);
@BeforeEach
void setup() {
this.handler = mock(ObservationHandler.class);
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig().observationHandler(this.handler);
this.registry = registry;
this.authorizationManager = mock(ReactiveAuthorizationManager.class);
this.tested = new ObservationReactiveAuthorizationManager<>(this.registry, this.authorizationManager);
}
@Test
void verifyWhenDefaultsThenObserves() {
given(this.handler.supportsContext(any())).willReturn(true);
given(this.authorizationManager.check(any(), any())).willReturn(Mono.just(this.grant));
this.tested.verify(this.token, this.object).block();
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.handler).onStart(captor.capture());
assertThat(captor.getValue().getName()).isEqualTo(AuthorizationObservationConvention.OBSERVATION_NAME);
assertThat(captor.getValue().getError()).isNull();
assertThat(captor.getValue()).isInstanceOf(AuthorizationObservationContext.class);
AuthorizationObservationContext<?> context = (AuthorizationObservationContext<?>) captor.getValue();
assertThat(context.getAuthentication()).isNull();
assertThat(context.getObject()).isEqualTo(this.object);
assertThat(context.getDecision()).isEqualTo(this.grant);
}
@Test
void verifyWhenErrorsThenObserves() {
given(this.handler.supportsContext(any())).willReturn(true);
given(this.authorizationManager.check(any(), any())).willReturn(Mono.just(this.deny));
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> this.tested.verify(this.token, this.object).block());
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.handler).onStart(captor.capture());
assertThat(captor.getValue().getName()).isEqualTo(AuthorizationObservationConvention.OBSERVATION_NAME);
assertThat(captor.getValue().getError()).isInstanceOf(AccessDeniedException.class);
assertThat(captor.getValue()).isInstanceOf(AuthorizationObservationContext.class);
AuthorizationObservationContext<?> context = (AuthorizationObservationContext<?>) captor.getValue();
assertThat(context.getAuthentication()).isNull();
assertThat(context.getObject()).isEqualTo(this.object);
assertThat(context.getDecision()).isEqualTo(this.deny);
}
@Test
void verifyWhenLooksUpAuthenticationThenObserves() {
given(this.handler.supportsContext(any())).willReturn(true);
given(this.authorizationManager.check(any(), any())).willAnswer((invocation) -> {
((Mono<Authentication>) invocation.getArgument(0)).block();
return Mono.just(this.grant);
});
this.tested.verify(this.token, this.object).block();
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
verify(this.handler).onStart(captor.capture());
assertThat(captor.getValue().getName()).isEqualTo(AuthorizationObservationConvention.OBSERVATION_NAME);
assertThat(captor.getValue().getError()).isNull();
AuthorizationObservationContext<?> context = (AuthorizationObservationContext<?>) captor.getValue();
assertThat(context.getAuthentication()).isEqualTo(this.token.block());
assertThat(context.getObject()).isEqualTo(this.object);
assertThat(context.getDecision()).isEqualTo(this.grant);
}
}