Make Observations Selectable
Closes gh-15678
This commit is contained in:
@@ -87,6 +87,7 @@ import org.springframework.security.authorization.method.PrePostTemplateDefaults
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.security.config.observation.SecurityObservationSettings;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.config.test.SpringTestParentApplicationContextExecutionListener;
|
||||
@@ -114,6 +115,7 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link PrePostMethodSecurityConfiguration}.
|
||||
@@ -1062,6 +1064,43 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||
verify(handler).onError(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void prePostMethodWhenExcludeAuthorizationObservationsThenUnobserved() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, ObservationRegistryConfig.class,
|
||||
SelectableObservationsConfig.class)
|
||||
.autowire();
|
||||
this.methodSecurityService.preAuthorizePermitAll();
|
||||
ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorize);
|
||||
verifyNoInteractions(handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void securedMethodWhenExcludeAuthorizationObservationsThenUnobserved() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, ObservationRegistryConfig.class,
|
||||
SelectableObservationsConfig.class)
|
||||
.autowire();
|
||||
this.methodSecurityService.securedUser();
|
||||
ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
|
||||
verifyNoInteractions(handler);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void jsr250MethodWhenExcludeAuthorizationObservationsThenUnobserved() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, ObservationRegistryConfig.class,
|
||||
SelectableObservationsConfig.class)
|
||||
.autowire();
|
||||
this.methodSecurityService.jsr250RolesAllowedUser();
|
||||
ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
|
||||
verifyNoInteractions(handler);
|
||||
}
|
||||
|
||||
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
|
||||
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
|
||||
}
|
||||
@@ -1742,4 +1781,14 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SelectableObservationsConfig {
|
||||
|
||||
@Bean
|
||||
SecurityObservationSettings observabilityDefaults() {
|
||||
return SecurityObservationSettings.withDefaults().shouldObserveAuthorizations(false).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,9 +33,11 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
@@ -55,6 +57,7 @@ import org.springframework.security.authorization.method.AuthorizeReturnObject;
|
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.security.config.observation.SecurityObservationSettings;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -69,6 +72,7 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
/**
|
||||
* @author Tadaya Tsuyukubo
|
||||
@@ -260,6 +264,27 @@ public class ReactiveMethodSecurityConfigurationTests {
|
||||
verify(handler).onError(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void prePostMethodWhenExcludeAuthorizationObservationsThenUnobserved() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceConfig.class, ObservationRegistryConfig.class,
|
||||
SelectableObservationsConfig.class)
|
||||
.autowire();
|
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
|
||||
Authentication user = TestAuthentication.authenticatedUser();
|
||||
StepVerifier
|
||||
.create(service.preAuthorizeUser().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user)))
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
ObservationHandler<?> handler = this.spring.getContext().getBean(ObservationHandler.class);
|
||||
StepVerifier
|
||||
.create(service.preAuthorizeAdmin().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user)))
|
||||
.expectError()
|
||||
.verify();
|
||||
verifyNoInteractions(handler);
|
||||
}
|
||||
|
||||
private static Consumer<User.UserBuilder> authorities(String... authorities) {
|
||||
return (builder) -> builder.authorities(authorities);
|
||||
}
|
||||
@@ -432,9 +457,37 @@ public class ReactiveMethodSecurityConfigurationTests {
|
||||
}
|
||||
|
||||
@Bean
|
||||
PrePostMethodSecurityConfigurationTests.ObservationRegistryPostProcessor observationRegistryPostProcessor(
|
||||
ObservationRegistryPostProcessor observationRegistryPostProcessor(
|
||||
ObjectProvider<ObservationHandler<Observation.Context>> handler) {
|
||||
return new PrePostMethodSecurityConfigurationTests.ObservationRegistryPostProcessor(handler);
|
||||
return new ObservationRegistryPostProcessor(handler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ObservationRegistryPostProcessor implements BeanPostProcessor {
|
||||
|
||||
private final ObjectProvider<ObservationHandler<Observation.Context>> handler;
|
||||
|
||||
ObservationRegistryPostProcessor(ObjectProvider<ObservationHandler<Observation.Context>> handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof ObservationRegistry registry) {
|
||||
registry.observationConfig().observationHandler(this.handler.getObject());
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SelectableObservationsConfig {
|
||||
|
||||
@Bean
|
||||
SecurityObservationSettings observabilityDefaults() {
|
||||
return SecurityObservationSettings.withDefaults().shouldObserveAuthorizations(false).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.security.config.annotation.web.AbstractRequestMatcher
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.security.config.observation.SecurityObservationSettings;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -80,6 +81,7 @@ import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
@@ -650,6 +652,18 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
||||
verify(handler).onError(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenExcludeAuthorizationObservationsThenUnobserved() throws Exception {
|
||||
this.spring
|
||||
.register(RoleUserConfig.class, BasicController.class, ObservationRegistryConfig.class,
|
||||
SelectableObservationsConfig.class)
|
||||
.autowire();
|
||||
ObservationHandler<Observation.Context> handler = this.spring.getContext().getBean(ObservationHandler.class);
|
||||
this.mvc.perform(get("/").with(user("user").roles("USER"))).andExpect(status().isOk());
|
||||
this.mvc.perform(get("/").with(user("user").roles("WRONG"))).andExpect(status().isForbidden());
|
||||
verifyNoInteractions(handler);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class GrantedAuthorityDefaultHasRoleConfig {
|
||||
@@ -1288,4 +1302,14 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SelectableObservationsConfig {
|
||||
|
||||
@Bean
|
||||
SecurityObservationSettings observabilityDefaults() {
|
||||
return SecurityObservationSettings.withDefaults().shouldObserveAuthorizations(false).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.observation.SecurityObservationSettings;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
@@ -63,6 +64,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
@@ -189,6 +191,26 @@ public class HttpBasicConfigurerTests {
|
||||
assertThat(context.getValue()).isInstanceOf(AuthenticationObservationContext.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpBasicWhenExcludeAuthenticationObservationsThenUnobserved() throws Exception {
|
||||
this.spring
|
||||
.register(HttpBasic.class, Users.class, Home.class, ObservationRegistryConfig.class,
|
||||
SelectableObservationsConfig.class)
|
||||
.autowire();
|
||||
ObservationHandler<Observation.Context> handler = this.spring.getContext().getBean(ObservationHandler.class);
|
||||
this.mvc.perform(get("/").with(httpBasic("user", "password")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string("user"));
|
||||
ArgumentCaptor<Observation.Context> context = ArgumentCaptor.forClass(Observation.Context.class);
|
||||
verify(handler, atLeastOnce()).onStart(context.capture());
|
||||
assertThat(context.getAllValues()).noneMatch((c) -> c instanceof AuthenticationObservationContext);
|
||||
context = ArgumentCaptor.forClass(Observation.Context.class);
|
||||
verify(handler, atLeastOnce()).onStop(context.capture());
|
||||
assertThat(context.getAllValues()).noneMatch((c) -> c instanceof AuthenticationObservationContext);
|
||||
this.mvc.perform(get("/").with(httpBasic("user", "wrong"))).andExpect(status().isUnauthorized());
|
||||
verify(handler, never()).onError(any());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class ObjectPostProcessorConfig {
|
||||
@@ -455,4 +477,14 @@ public class HttpBasicConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SelectableObservationsConfig {
|
||||
|
||||
@Bean
|
||||
SecurityObservationSettings observabilityDefaults() {
|
||||
return SecurityObservationSettings.withDefaults().shouldObserveAuthentications(false).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
|
||||
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
|
||||
import org.springframework.security.config.observation.SecurityObservationSettings;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
@@ -106,6 +107,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.springframework.security.web.csrf.CsrfTokenAssert.assertThatCsrfToken;
|
||||
|
||||
public class WebSocketMessageBrokerSecurityConfigurationTests {
|
||||
@@ -414,6 +416,28 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
|
||||
verify(observationHandler).onError(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendMessageWhenExcludeAuthorizationObservationsThenUnobserved() {
|
||||
loadConfig(WebSocketSecurityConfig.class, ObservationRegistryConfig.class, SelectableObservationsConfig.class);
|
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
|
||||
headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE);
|
||||
Message<?> message = message(headers, "/authenticated");
|
||||
headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
|
||||
clientInboundChannel().send(message);
|
||||
ObservationHandler<Observation.Context> observationHandler = this.context.getBean(ObservationHandler.class);
|
||||
headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
|
||||
headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE);
|
||||
message = message(headers, "/denyAll");
|
||||
headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token);
|
||||
try {
|
||||
clientInboundChannel().send(message);
|
||||
}
|
||||
catch (MessageDeliveryException ex) {
|
||||
// okay
|
||||
}
|
||||
verifyNoInteractions(observationHandler);
|
||||
}
|
||||
|
||||
private void assertHandshake(HttpServletRequest request) {
|
||||
TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class);
|
||||
assertThatCsrfToken(handshakeHandler.attributes.get(CsrfToken.class.getName())).isEqualTo(this.token);
|
||||
@@ -968,4 +992,14 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SelectableObservationsConfig {
|
||||
|
||||
@Bean
|
||||
SecurityObservationSettings observabilityDefaults() {
|
||||
return SecurityObservationSettings.withDefaults().shouldObserveAuthorizations(false).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.observation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link SecurityObservationSettings}
|
||||
*/
|
||||
public class SecurityObservationSettingsTests {
|
||||
|
||||
@Test
|
||||
void withDefaultsThenFilterOffAuthenticationOnAuthorizationOn() {
|
||||
SecurityObservationSettings defaults = SecurityObservationSettings.withDefaults().build();
|
||||
assertThat(defaults.shouldObserveRequests()).isFalse();
|
||||
assertThat(defaults.shouldObserveAuthentications()).isTrue();
|
||||
assertThat(defaults.shouldObserveAuthorizations()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noObservationsWhenConstructedThenAllOff() {
|
||||
SecurityObservationSettings defaults = SecurityObservationSettings.noObservations();
|
||||
assertThat(defaults.shouldObserveRequests()).isFalse();
|
||||
assertThat(defaults.shouldObserveAuthentications()).isFalse();
|
||||
assertThat(defaults.shouldObserveAuthorizations()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void withDefaultsWhenExclusionsThenInstanceReflects() {
|
||||
SecurityObservationSettings defaults = SecurityObservationSettings.withDefaults()
|
||||
.shouldObserveAuthentications(false)
|
||||
.shouldObserveAuthorizations(false)
|
||||
.shouldObserveRequests(true)
|
||||
.build();
|
||||
assertThat(defaults.shouldObserveRequests()).isTrue();
|
||||
assertThat(defaults.shouldObserveAuthentications()).isFalse();
|
||||
assertThat(defaults.shouldObserveAuthorizations()).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user