Skip to content

Commit 0f7ceeb

Browse files
committed
Add ObservationRegistry Tests
Issue spring-projectsgh-11989 Issue spring-projectsgh-11990
1 parent 127db71 commit 0f7ceeb

7 files changed

+378
-1
lines changed

config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

+87
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
import java.util.function.Consumer;
2929
import java.util.function.Supplier;
3030

31+
import io.micrometer.observation.Observation;
32+
import io.micrometer.observation.ObservationHandler;
33+
import io.micrometer.observation.ObservationRegistry;
34+
import io.micrometer.observation.ObservationTextPublisher;
3135
import jakarta.annotation.security.DenyAll;
3236
import org.aopalliance.intercept.MethodInterceptor;
3337
import org.aopalliance.intercept.MethodInvocation;
@@ -40,9 +44,12 @@
4044
import org.springframework.aop.config.AopConfigUtils;
4145
import org.springframework.aop.support.DefaultPointcutAdvisor;
4246
import org.springframework.aop.support.JdkRegexpMethodPointcut;
47+
import org.springframework.beans.BeansException;
4348
import org.springframework.beans.factory.FactoryBean;
49+
import org.springframework.beans.factory.ObjectProvider;
4450
import org.springframework.beans.factory.annotation.Autowired;
4551
import org.springframework.beans.factory.config.BeanDefinition;
52+
import org.springframework.beans.factory.config.BeanPostProcessor;
4653
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
4754
import org.springframework.context.annotation.AdviceMode;
4855
import org.springframework.context.annotation.Bean;
@@ -1018,6 +1025,43 @@ public void methodWhenMetaAnnotationPropertiesHasClassProperties() {
10181025
assertThat(service.getIdPath("uid")).isEqualTo("uid");
10191026
}
10201027

1028+
@Test
1029+
@WithMockUser
1030+
public void prePostMethodWhenObservationRegistryThenObserved() {
1031+
this.spring.register(MethodSecurityServiceEnabledConfig.class, ObservationRegistryConfig.class).autowire();
1032+
this.methodSecurityService.preAuthorizePermitAll();
1033+
ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
1034+
verify(handler).onStart(any());
1035+
verify(handler).onStop(any());
1036+
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorize);
1037+
verify(handler).onError(any());
1038+
}
1039+
1040+
@Test
1041+
@WithMockUser
1042+
public void securedMethodWhenObservationRegistryThenObserved() {
1043+
this.spring.register(MethodSecurityServiceEnabledConfig.class, ObservationRegistryConfig.class).autowire();
1044+
this.methodSecurityService.securedUser();
1045+
ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
1046+
verify(handler).onStart(any());
1047+
verify(handler).onStop(any());
1048+
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured);
1049+
verify(handler).onError(any());
1050+
}
1051+
1052+
@Test
1053+
@WithMockUser
1054+
public void jsr250MethodWhenObservationRegistryThenObserved() {
1055+
this.spring.register(MethodSecurityServiceEnabledConfig.class, ObservationRegistryConfig.class).autowire();
1056+
this.methodSecurityService.jsr250RolesAllowedUser();
1057+
ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
1058+
verify(handler).onStart(any());
1059+
verify(handler).onStop(any());
1060+
assertThatExceptionOfType(AccessDeniedException.class)
1061+
.isThrownBy(this.methodSecurityService::jsr250RolesAllowed);
1062+
verify(handler).onError(any());
1063+
}
1064+
10211065
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
10221066
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
10231067
}
@@ -1655,4 +1699,47 @@ public Class<?> getObjectType() {
16551699

16561700
}
16571701

1702+
@Configuration
1703+
static class ObservationRegistryConfig {
1704+
1705+
private final ObservationRegistry registry = ObservationRegistry.create();
1706+
1707+
private final ObservationHandler<Observation.Context> handler = spy(new ObservationTextPublisher());
1708+
1709+
@Bean
1710+
ObservationRegistry observationRegistry() {
1711+
return this.registry;
1712+
}
1713+
1714+
@Bean
1715+
ObservationHandler<Observation.Context> observationHandler() {
1716+
return this.handler;
1717+
}
1718+
1719+
@Bean
1720+
ObservationRegistryPostProcessor observationRegistryPostProcessor(
1721+
ObjectProvider<ObservationHandler<Observation.Context>> handler) {
1722+
return new ObservationRegistryPostProcessor(handler);
1723+
}
1724+
1725+
}
1726+
1727+
static class ObservationRegistryPostProcessor implements BeanPostProcessor {
1728+
1729+
private final ObjectProvider<ObservationHandler<Observation.Context>> handler;
1730+
1731+
ObservationRegistryPostProcessor(ObjectProvider<ObservationHandler<Observation.Context>> handler) {
1732+
this.handler = handler;
1733+
}
1734+
1735+
@Override
1736+
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
1737+
if (bean instanceof ObservationRegistry registry) {
1738+
registry.observationConfig().observationHandler(this.handler.getObject());
1739+
}
1740+
return bean;
1741+
}
1742+
1743+
}
1744+
16581745
}

config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java

+51
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,17 @@
2323
import java.util.function.Consumer;
2424
import java.util.function.Function;
2525

26+
import io.micrometer.observation.Observation;
27+
import io.micrometer.observation.ObservationHandler;
28+
import io.micrometer.observation.ObservationRegistry;
29+
import io.micrometer.observation.ObservationTextPublisher;
2630
import org.junit.jupiter.api.Test;
2731
import org.junit.jupiter.api.extension.ExtendWith;
2832
import reactor.core.publisher.Flux;
2933
import reactor.core.publisher.Mono;
3034
import reactor.test.StepVerifier;
3135

36+
import org.springframework.beans.factory.ObjectProvider;
3237
import org.springframework.beans.factory.annotation.Autowired;
3338
import org.springframework.beans.factory.config.BeanDefinition;
3439
import org.springframework.context.annotation.Bean;
@@ -62,6 +67,7 @@
6267
import static org.mockito.Mockito.clearInvocations;
6368
import static org.mockito.Mockito.mock;
6469
import static org.mockito.Mockito.never;
70+
import static org.mockito.Mockito.spy;
6571
import static org.mockito.Mockito.verify;
6672

6773
/**
@@ -235,6 +241,25 @@ void getUserWhenNotAuthorizedThenHandlerUsesCustomAuthorizationDecision() {
235241
verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class));
236242
}
237243

244+
@Test
245+
public void prePostMethodWhenObservationRegistryThenObserved() {
246+
this.spring.register(MethodSecurityServiceConfig.class, ObservationRegistryConfig.class).autowire();
247+
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
248+
Authentication user = TestAuthentication.authenticatedUser();
249+
StepVerifier
250+
.create(service.preAuthorizeUser().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user)))
251+
.expectNextCount(1)
252+
.verifyComplete();
253+
ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
254+
verify(handler).onStart(any());
255+
verify(handler).onStop(any());
256+
StepVerifier
257+
.create(service.preAuthorizeAdmin().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user)))
258+
.expectError()
259+
.verify();
260+
verify(handler).onError(any());
261+
}
262+
238263
private static Consumer<User.UserBuilder> authorities(String... authorities) {
239264
return (builder) -> builder.authorities(authorities);
240265
}
@@ -388,4 +413,30 @@ MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
388413

389414
}
390415

416+
@Configuration
417+
@EnableReactiveMethodSecurity
418+
static class ObservationRegistryConfig {
419+
420+
private final ObservationRegistry registry = ObservationRegistry.create();
421+
422+
private final ObservationHandler<Observation.Context> handler = spy(new ObservationTextPublisher());
423+
424+
@Bean
425+
ObservationRegistry observationRegistry() {
426+
return this.registry;
427+
}
428+
429+
@Bean
430+
ObservationHandler<Observation.Context> observationHandler() {
431+
return this.handler;
432+
}
433+
434+
@Bean
435+
PrePostMethodSecurityConfigurationTests.ObservationRegistryPostProcessor observationRegistryPostProcessor(
436+
ObjectProvider<ObservationHandler<Observation.Context>> handler) {
437+
return new PrePostMethodSecurityConfigurationTests.ObservationRegistryPostProcessor(handler);
438+
}
439+
440+
}
441+
391442
}

config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java

+6
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
@ReactiveMethodSecurityService.Mask("classmask")
4949
public interface ReactiveMethodSecurityService {
5050

51+
@PreAuthorize("hasRole('USER')")
52+
Mono<String> preAuthorizeUser();
53+
54+
@PreAuthorize("hasRole('ADMIN')")
55+
Mono<String> preAuthorizeAdmin();
56+
5157
@PreAuthorize("hasRole('ADMIN')")
5258
@HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
5359
Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber);

config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java

+10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@
2525

2626
public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurityService {
2727

28+
@Override
29+
public Mono<String> preAuthorizeUser() {
30+
return Mono.just("user");
31+
}
32+
33+
@Override
34+
public Mono<String> preAuthorizeAdmin() {
35+
return Mono.just("admin");
36+
}
37+
2838
@Override
2939
public Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber) {
3040
return Mono.just(cardNumber);

config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

+77-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,20 @@
1818

1919
import java.util.function.Supplier;
2020

21+
import io.micrometer.observation.Observation;
22+
import io.micrometer.observation.ObservationHandler;
23+
import io.micrometer.observation.ObservationRegistry;
24+
import io.micrometer.observation.ObservationTextPublisher;
2125
import jakarta.servlet.http.HttpServletRequest;
2226
import org.junit.jupiter.api.Test;
2327
import org.junit.jupiter.api.extension.ExtendWith;
28+
import org.mockito.ArgumentCaptor;
2429

30+
import org.springframework.beans.BeansException;
2531
import org.springframework.beans.factory.BeanCreationException;
32+
import org.springframework.beans.factory.ObjectProvider;
2633
import org.springframework.beans.factory.annotation.Autowired;
34+
import org.springframework.beans.factory.config.BeanPostProcessor;
2735
import org.springframework.context.annotation.Bean;
2836
import org.springframework.context.annotation.Configuration;
2937
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
@@ -33,6 +41,7 @@
3341
import org.springframework.security.authorization.AuthorizationDecision;
3442
import org.springframework.security.authorization.AuthorizationEventPublisher;
3543
import org.springframework.security.authorization.AuthorizationManager;
44+
import org.springframework.security.authorization.AuthorizationObservationContext;
3645
import org.springframework.security.config.annotation.ObjectPostProcessor;
3746
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
3847
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -43,6 +52,7 @@
4352
import org.springframework.security.core.Authentication;
4453
import org.springframework.security.core.authority.AuthorityUtils;
4554
import org.springframework.security.core.authority.SimpleGrantedAuthority;
55+
import org.springframework.security.core.userdetails.User;
4656
import org.springframework.security.core.userdetails.UserDetails;
4757
import org.springframework.security.core.userdetails.UserDetailsService;
4858
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@@ -63,8 +73,10 @@
6373
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
6474
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
6575

76+
import static org.assertj.core.api.Assertions.assertThat;
6677
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
6778
import static org.mockito.Mockito.any;
79+
import static org.mockito.Mockito.atLeastOnce;
6880
import static org.mockito.Mockito.mock;
6981
import static org.mockito.Mockito.spy;
7082
import static org.mockito.Mockito.verify;
@@ -153,7 +165,8 @@ public void configureMvcMatcherAccessAuthorizationManagerWhenNullThenException()
153165
@Test
154166
public void configureWhenObjectPostProcessorRegisteredThenInvokedOnAuthorizationManagerAndAuthorizationFilter() {
155167
this.spring.register(ObjectPostProcessorConfig.class).autowire();
156-
ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class);
168+
ObjectPostProcessor<Object> objectPostProcessor = this.spring.getContext()
169+
.getBean(ObjectPostProcessorConfig.class).objectPostProcessor;
157170
verify(objectPostProcessor).postProcess(any(RequestMatcherDelegatingAuthorizationManager.class));
158171
verify(objectPostProcessor).postProcess(any(AuthorizationFilter.class));
159172
}
@@ -623,6 +636,20 @@ public void getWhenNotConfigAndNotAuthenticatedThenRespondsWithOk() throws Excep
623636
this.mvc.perform(requestWithUser).andExpect(status().isOk());
624637
}
625638

639+
@Test
640+
public void getWhenObservationRegistryThenObserves() throws Exception {
641+
this.spring.register(RoleUserConfig.class, BasicController.class, ObservationRegistryConfig.class).autowire();
642+
ObservationHandler<Observation.Context> handler = this.spring.getContext().getBean(ObservationHandler.class);
643+
this.mvc.perform(get("/").with(user("user").roles("USER"))).andExpect(status().isOk());
644+
ArgumentCaptor<Observation.Context> context = ArgumentCaptor.forClass(Observation.Context.class);
645+
verify(handler, atLeastOnce()).onStart(context.capture());
646+
assertThat(context.getAllValues()).anyMatch((c) -> c instanceof AuthorizationObservationContext);
647+
verify(handler, atLeastOnce()).onStop(context.capture());
648+
assertThat(context.getAllValues()).anyMatch((c) -> c instanceof AuthorizationObservationContext);
649+
this.mvc.perform(get("/").with(user("user").roles("WRONG"))).andExpect(status().isForbidden());
650+
verify(handler).onError(any());
651+
}
652+
626653
@Configuration
627654
@EnableWebSecurity
628655
static class GrantedAuthorityDefaultHasRoleConfig {
@@ -1015,6 +1042,12 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
10151042
// @formatter:on
10161043
}
10171044

1045+
@Bean
1046+
UserDetailsService users() {
1047+
return new InMemoryUserDetailsManager(
1048+
User.withUsername("user").password("{noop}password").roles("USER").build());
1049+
}
1050+
10181051
}
10191052

10201053
@Configuration
@@ -1212,4 +1245,47 @@ void rootPost() {
12121245

12131246
}
12141247

1248+
@Configuration
1249+
static class ObservationRegistryConfig {
1250+
1251+
private final ObservationRegistry registry = ObservationRegistry.create();
1252+
1253+
private final ObservationHandler<Observation.Context> handler = spy(new ObservationTextPublisher());
1254+
1255+
@Bean
1256+
ObservationRegistry observationRegistry() {
1257+
return this.registry;
1258+
}
1259+
1260+
@Bean
1261+
ObservationHandler<Observation.Context> observationHandler() {
1262+
return this.handler;
1263+
}
1264+
1265+
@Bean
1266+
ObservationRegistryPostProcessor observationRegistryPostProcessor(
1267+
ObjectProvider<ObservationHandler<Observation.Context>> handler) {
1268+
return new ObservationRegistryPostProcessor(handler);
1269+
}
1270+
1271+
}
1272+
1273+
static class ObservationRegistryPostProcessor implements BeanPostProcessor {
1274+
1275+
private final ObjectProvider<ObservationHandler<Observation.Context>> handler;
1276+
1277+
ObservationRegistryPostProcessor(ObjectProvider<ObservationHandler<Observation.Context>> handler) {
1278+
this.handler = handler;
1279+
}
1280+
1281+
@Override
1282+
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
1283+
if (bean instanceof ObservationRegistry registry) {
1284+
registry.observationConfig().observationHandler(this.handler.getObject());
1285+
}
1286+
return bean;
1287+
}
1288+
1289+
}
1290+
12151291
}

0 commit comments

Comments
 (0)