diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java index fe0a85b9ac..8786279b0a 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -17,16 +17,16 @@ package org.springframework.security.config.annotation.method.configuration; import org.springframework.aop.Advisor; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.method.AuthorizationManagerAfterMethodInterceptor; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager; @@ -45,7 +45,7 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults; */ @Configuration(proxyBeanMethods = false) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) -final class PrePostMethodSecurityConfiguration implements ApplicationContextAware { +final class PrePostMethodSecurityConfiguration { private final PreFilterAuthorizationMethodInterceptor preFilterAuthorizationMethodInterceptor = new PreFilterAuthorizationMethodInterceptor(); @@ -61,7 +61,8 @@ final class PrePostMethodSecurityConfiguration implements ApplicationContextAwar private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); - PrePostMethodSecurityConfiguration() { + @Autowired + PrePostMethodSecurityConfiguration(ApplicationContext context) { this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler); this.preAuthorizeAuthorizationMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor .preAuthorize(this.preAuthorizeAuthorizationManager); @@ -70,6 +71,10 @@ final class PrePostMethodSecurityConfiguration implements ApplicationContextAwar .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 @@ -109,9 +114,10 @@ final class PrePostMethodSecurityConfiguration implements ApplicationContextAwar this.expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix()); } - @Override - public void setApplicationContext(ApplicationContext context) throws BeansException { - this.expressionHandler.setApplicationContext(context); + @Autowired(required = false) + void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) { + this.preAuthorizeAuthorizationMethodInterceptor.setAuthorizationEventPublisher(eventPublisher); + this.postAuthorizeAuthorizaitonMethodInterceptor.setAuthorizationEventPublisher(eventPublisher); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java index ee7d7a4697..2917f00f80 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -26,7 +26,9 @@ import org.springframework.http.HttpMethod; import org.springframework.security.authorization.AuthenticatedAuthorizationManager; 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.SpringAuthorizationEventPublisher; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; @@ -52,12 +54,20 @@ public final class AuthorizeHttpRequestsConfigurer 0) { + this.publisher = context.getBean(AuthorizationEventPublisher.class); + } + else { + this.publisher = new SpringAuthorizationEventPublisher(context); + } } /** @@ -74,6 +84,7 @@ public final class AuthorizeHttpRequestsConfigurer authorizationManager = this.registry.createAuthorizationManager(); AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager); + authorizationFilter.setAuthorizationEventPublisher(this.publisher); http.addFilter(postProcess(authorizationFilter)); } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java index 4be39df6ea..4af1f89cf6 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.Supplier; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -32,6 +33,7 @@ import org.springframework.aop.support.JdkRegexpMethodPointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Role; import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.security.access.AccessDeniedException; @@ -43,9 +45,11 @@ import org.springframework.security.access.annotation.Jsr250BusinessServiceImpl; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; +import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; @@ -58,6 +62,9 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; 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.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link PrePostMethodSecurityConfiguration}. @@ -350,6 +357,27 @@ public class PrePostMethodSecurityConfigurationTests { .isThrownBy(() -> this.businessService.repeatedAnnotations()); } + @WithMockUser + @Test + public void preAuthorizeWhenAuthorizationEventPublisherThenUses() { + this.spring.register(MethodSecurityServiceConfig.class, AuthorizationEventPublisherConfig.class).autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> this.methodSecurityService.preAuthorize()); + AuthorizationEventPublisher publisher = this.spring.getContext().getBean(AuthorizationEventPublisher.class); + verify(publisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocation.class), + any(AuthorizationDecision.class)); + } + + @WithMockUser + @Test + public void postAuthorizeWhenAuthorizationEventPublisherThenUses() { + this.spring.register(MethodSecurityServiceConfig.class, AuthorizationEventPublisherConfig.class).autowire(); + this.methodSecurityService.postAnnotation("grant"); + AuthorizationEventPublisher publisher = this.spring.getContext().getBean(AuthorizationEventPublisher.class); + verify(publisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocationResult.class), + any(AuthorizationDecision.class)); + } + // gh-10305 @WithMockUser @Test @@ -484,4 +512,16 @@ public class PrePostMethodSecurityConfigurationTests { } + @Configuration + static class AuthorizationEventPublisherConfig { + + private final AuthorizationEventPublisher publisher = mock(AuthorizationEventPublisher.class); + + @Bean + AuthorizationEventPublisher authorizationEventPublisher() { + return this.publisher; + } + + } + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java index 29e097e580..e3c2a96109 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -16,12 +16,19 @@ package org.springframework.security.config.annotation.web.configurers; +import java.util.function.Supplier; + +import javax.servlet.http.HttpServletRequest; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; @@ -129,9 +136,9 @@ public class AuthorizeHttpRequestsConfigurerTests { @Test public void configureWhenObjectPostProcessorRegisteredThenInvokedOnAuthorizationManagerAndAuthorizationFilter() { this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor) - .postProcess(any(RequestMatcherDelegatingAuthorizationManager.class)); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(AuthorizationFilter.class)); + ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class); + verify(objectPostProcessor).postProcess(any(RequestMatcherDelegatingAuthorizationManager.class)); + verify(objectPostProcessor).postProcess(any(AuthorizationFilter.class)); } @Test @@ -369,6 +376,15 @@ public class AuthorizeHttpRequestsConfigurerTests { this.mvc.perform(get("/")).andExpect(status().isUnauthorized()); } + @Test + public void getWhenCustomAuthorizationEventPublisherThenUses() throws Exception { + this.spring.register(AuthenticatedConfig.class, AuthorizationEventPublisherConfig.class).autowire(); + this.mvc.perform(get("/")).andExpect(status().isUnauthorized()); + AuthorizationEventPublisher publisher = this.spring.getContext().getBean(AuthorizationEventPublisher.class); + verify(publisher).publishAuthorizationEvent(any(Supplier.class), any(HttpServletRequest.class), + any(AuthorizationDecision.class)); + } + @Test public void getWhenAnyRequestAuthenticatedConfiguredAndUserLoggedInThenRespondsWithOk() throws Exception { this.spring.register(AuthenticatedConfig.class, BasicController.class).autowire(); @@ -495,7 +511,7 @@ public class AuthorizeHttpRequestsConfigurerTests { @EnableWebSecurity static class ObjectPostProcessorConfig { - static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); + ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -509,8 +525,8 @@ public class AuthorizeHttpRequestsConfigurerTests { } @Bean - static ObjectPostProcessor objectPostProcessor() { - return objectPostProcessor; + ObjectPostProcessor objectPostProcessor() { + return this.objectPostProcessor; } } @@ -698,6 +714,18 @@ public class AuthorizeHttpRequestsConfigurerTests { } + @Configuration + static class AuthorizationEventPublisherConfig { + + private final AuthorizationEventPublisher publisher = mock(AuthorizationEventPublisher.class); + + @Bean + AuthorizationEventPublisher authorizationEventPublisher() { + return this.publisher; + } + + } + @RestController static class BasicController { diff --git a/core/src/main/java/org/springframework/security/authorization/AuthorizationEventPublisher.java b/core/src/main/java/org/springframework/security/authorization/AuthorizationEventPublisher.java index 4a4cc4b22d..de972c59b2 100644 --- a/core/src/main/java/org/springframework/security/authorization/AuthorizationEventPublisher.java +++ b/core/src/main/java/org/springframework/security/authorization/AuthorizationEventPublisher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -16,14 +16,34 @@ package org.springframework.security.authorization; +import java.util.function.Supplier; + +import org.springframework.security.authorization.event.AuthorizationDeniedEvent; +import org.springframework.security.authorization.event.AuthorizationGrantedEvent; +import org.springframework.security.core.Authentication; + /** + * A contract for publishing authorization events + * * @author Parikshit Dutta - * @since 5.5 + * @author Josh Cummings + * @since 5.7 + * @see AuthorizationManager */ public interface AuthorizationEventPublisher { - void publishAuthorizationSuccess(AuthorizationDecision authorizationDecision); - - void publishAuthorizationFailure(AuthorizationDecision authorizationDecision); + /** + * Publish the given details in the form of an event, typically + * {@link AuthorizationGrantedEvent} or {@link AuthorizationDeniedEvent}. + * + * Note that success events can be very noisy if enabled by default. Because of this + * implementations may choose to drop success events by default. + * @param authentication a {@link Supplier} for the current user + * @param object the secured object + * @param decision the decision about whether the user may access the secured object + * @param the secured object's type + */ + void publishAuthorizationEvent(Supplier authentication, T object, + AuthorizationDecision decision); } diff --git a/core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationEventPublisher.java b/core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationEventPublisher.java deleted file mode 100644 index 7e2079e4e8..0000000000 --- a/core/src/main/java/org/springframework/security/authorization/DefaultAuthorizationEventPublisher.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-2021 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 org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.security.authorization.event.AuthorizationFailureEvent; -import org.springframework.security.authorization.event.AuthorizationSuccessEvent; - -/** - * Default implementation of {@link AuthorizationEventPublisher} - * - * @author Parikshit Dutta - * @since 5.5 - */ -public class DefaultAuthorizationEventPublisher implements AuthorizationEventPublisher, ApplicationEventPublisherAware { - - private ApplicationEventPublisher applicationEventPublisher; - - public DefaultAuthorizationEventPublisher() { - this(null); - } - - public DefaultAuthorizationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - - @Override - public void publishAuthorizationSuccess(AuthorizationDecision authorizationDecision) { - if (this.applicationEventPublisher != null) { - this.applicationEventPublisher.publishEvent(new AuthorizationSuccessEvent(authorizationDecision)); - } - } - - @Override - public void publishAuthorizationFailure(AuthorizationDecision authorizationDecision) { - if (this.applicationEventPublisher != null) { - this.applicationEventPublisher.publishEvent(new AuthorizationFailureEvent(authorizationDecision)); - } - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/SpringAuthorizationEventPublisher.java b/core/src/main/java/org/springframework/security/authorization/SpringAuthorizationEventPublisher.java new file mode 100644 index 0000000000..2ef21a2873 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/SpringAuthorizationEventPublisher.java @@ -0,0 +1,65 @@ +/* + * 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 org.springframework.context.ApplicationEventPublisher; +import org.springframework.security.authorization.event.AuthorizationDeniedEvent; +import org.springframework.security.authorization.event.AuthorizationGrantedEvent; +import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; + +/** + * An implementation of {@link AuthorizationEventPublisher} that uses Spring's event + * publishing support. + * + * Because {@link AuthorizationGrantedEvent}s typically require additional business logic + * to decide whether to publish, this implementation only publishes + * {@link AuthorizationDeniedEvent}s. + * + * @author Parikshit Dutta + * @author Josh Cummings + * @since 5.7 + */ +public final class SpringAuthorizationEventPublisher implements AuthorizationEventPublisher { + + private final ApplicationEventPublisher eventPublisher; + + /** + * Construct this publisher using Spring's {@link ApplicationEventPublisher} + * @param eventPublisher + */ + public SpringAuthorizationEventPublisher(ApplicationEventPublisher eventPublisher) { + Assert.notNull(eventPublisher, "eventPublisher cannot be null"); + this.eventPublisher = eventPublisher; + } + + /** + * {@inheritDoc} + */ + @Override + public void publishAuthorizationEvent(Supplier authentication, T object, + AuthorizationDecision decision) { + if (decision == null || decision.isGranted()) { + return; + } + AuthorizationDeniedEvent failure = new AuthorizationDeniedEvent<>(authentication, object, decision); + this.eventPublisher.publishEvent(failure); + } + +} diff --git a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationFailureEvent.java b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java similarity index 53% rename from core/src/main/java/org/springframework/security/authorization/event/AuthorizationFailureEvent.java rename to core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java index d1f7e6327b..0b514fd32f 100644 --- a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationFailureEvent.java +++ b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -16,19 +16,37 @@ package org.springframework.security.authorization.event; +import java.util.function.Supplier; + import org.springframework.context.ApplicationEvent; import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.core.Authentication; /** * An {@link ApplicationEvent} which indicates failed authorization. * * @author Parikshit Dutta - * @since 5.5 + * @author Josh Cummings + * @since 5.7 */ -public class AuthorizationFailureEvent extends ApplicationEvent { +public class AuthorizationDeniedEvent extends ApplicationEvent { - public AuthorizationFailureEvent(AuthorizationDecision authorizationDecision) { - super(authorizationDecision); + private final Supplier authentication; + + private final AuthorizationDecision decision; + + public AuthorizationDeniedEvent(Supplier authentication, T object, AuthorizationDecision decision) { + super(object); + this.authentication = authentication; + this.decision = decision; + } + + public Supplier getAuthentication() { + return this.authentication; + } + + public AuthorizationDecision getAuthorizationDecision() { + return this.decision; } } diff --git a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationSuccessEvent.java b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java similarity index 50% rename from core/src/main/java/org/springframework/security/authorization/event/AuthorizationSuccessEvent.java rename to core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java index 00f82b0ea7..4e210214e1 100644 --- a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationSuccessEvent.java +++ b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -16,19 +16,40 @@ package org.springframework.security.authorization.event; +import java.util.function.Supplier; + import org.springframework.context.ApplicationEvent; import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; /** * An {@link ApplicationEvent} which indicates successful authorization. * * @author Parikshit Dutta - * @since 5.5 + * @author Josh Cummings + * @since 5.7 */ -public class AuthorizationSuccessEvent extends ApplicationEvent { +public class AuthorizationGrantedEvent extends ApplicationEvent { - public AuthorizationSuccessEvent(AuthorizationDecision authorizationDecision) { - super(authorizationDecision); + private final Supplier authentication; + + private final AuthorizationDecision decision; + + public AuthorizationGrantedEvent(Supplier authentication, T object, + AuthorizationDecision decision) { + super(object); + Assert.notNull(authentication, "authentication supplier cannot be null"); + this.authentication = authentication; + this.decision = decision; + } + + public Supplier getAuthentication() { + return this.authentication; + } + + public AuthorizationDecision getAuthorizationDecision() { + return this.decision; } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java index e93777aaa0..91662e07e4 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -33,6 +33,7 @@ import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -66,6 +67,8 @@ public final class AuthorizationManagerAfterMethodInterceptor private int order; + private AuthorizationEventPublisher eventPublisher = AuthorizationManagerAfterMethodInterceptor::noPublish; + /** * Creates an instance. * @param pointcut the {@link Pointcut} to use @@ -122,6 +125,17 @@ public final class AuthorizationManagerAfterMethodInterceptor this.order = order; } + /** + * Use this {@link AuthorizationEventPublisher} to publish the + * {@link AuthorizationManager} result. + * @param eventPublisher + * @since 5.7 + */ + public void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) { + Assert.notNull(eventPublisher, "eventPublisher cannot be null"); + this.eventPublisher = eventPublisher; + } + /** * {@inheritDoc} */ @@ -142,8 +156,9 @@ public final class AuthorizationManagerAfterMethodInterceptor private void attemptAuthorization(MethodInvocation mi, Object result) { this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi)); - AuthorizationDecision decision = this.authorizationManager.check(AUTHENTICATION_SUPPLIER, - new MethodInvocationResult(mi, result)); + MethodInvocationResult object = new MethodInvocationResult(mi, result); + AuthorizationDecision decision = this.authorizationManager.check(AUTHENTICATION_SUPPLIER, object); + this.eventPublisher.publishAuthorizationEvent(AUTHENTICATION_SUPPLIER, object, decision); if (decision != null && !decision.isGranted()) { this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager " + this.authorizationManager + " and decision " + decision)); @@ -152,4 +167,9 @@ public final class AuthorizationManagerAfterMethodInterceptor this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi)); } + private static void noPublish(Supplier authentication, T object, + AuthorizationDecision decision) { + + } + } diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java index 38b6f03b1b..808b2a9c98 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -38,6 +38,7 @@ import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -71,6 +72,8 @@ public final class AuthorizationManagerBeforeMethodInterceptor private int order = AuthorizationInterceptorsOrder.FIRST.getOrder(); + private AuthorizationEventPublisher eventPublisher = AuthorizationManagerBeforeMethodInterceptor::noPublish; + /** * Creates an instance. * @param pointcut the {@link Pointcut} to use @@ -168,6 +171,17 @@ public final class AuthorizationManagerBeforeMethodInterceptor this.order = order; } + /** + * Use this {@link AuthorizationEventPublisher} to publish the + * {@link AuthorizationManager} result. + * @param eventPublisher + * @since 5.7 + */ + public void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) { + Assert.notNull(eventPublisher, "eventPublisher cannot be null"); + this.eventPublisher = eventPublisher; + } + /** * {@inheritDoc} */ @@ -189,6 +203,7 @@ public final class AuthorizationManagerBeforeMethodInterceptor private void attemptAuthorization(MethodInvocation mi) { this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi)); AuthorizationDecision decision = this.authorizationManager.check(AUTHENTICATION_SUPPLIER, mi); + this.eventPublisher.publishAuthorizationEvent(AUTHENTICATION_SUPPLIER, mi, decision); if (decision != null && !decision.isGranted()) { this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager " + this.authorizationManager + " and decision " + decision)); @@ -197,4 +212,9 @@ public final class AuthorizationManagerBeforeMethodInterceptor this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi)); } + private static void noPublish(Supplier authentication, T object, + AuthorizationDecision decision) { + + } + } diff --git a/core/src/test/java/org/springframework/security/authorization/DefaultAuthorizationEventPublisherTests.java b/core/src/test/java/org/springframework/security/authorization/DefaultAuthorizationEventPublisherTests.java deleted file mode 100644 index d2e9131bab..0000000000 --- a/core/src/test/java/org/springframework/security/authorization/DefaultAuthorizationEventPublisherTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2002-2021 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 org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.security.authorization.event.AuthorizationFailureEvent; -import org.springframework.security.authorization.event.AuthorizationSuccessEvent; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link DefaultAuthorizationEventPublisher} - * - * @author Parikshit Dutta - */ -public class DefaultAuthorizationEventPublisherTests { - - ApplicationEventPublisher applicationEventPublisher; - - DefaultAuthorizationEventPublisher authorizationEventPublisher; - - @BeforeEach - public void init() { - this.applicationEventPublisher = mock(ApplicationEventPublisher.class); - this.authorizationEventPublisher = new DefaultAuthorizationEventPublisher(); - this.authorizationEventPublisher.setApplicationEventPublisher(this.applicationEventPublisher); - } - - @Test - public void testAuthenticationSuccessIsPublished() { - this.authorizationEventPublisher.publishAuthorizationSuccess(mock(AuthorizationDecision.class)); - verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationSuccessEvent.class)); - } - - @Test - public void testAuthenticationFailureIsPublished() { - this.authorizationEventPublisher.publishAuthorizationFailure(mock(AuthorizationDecision.class)); - verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationFailureEvent.class)); - } - - @Test - public void testNullPublisherNotInvoked() { - this.authorizationEventPublisher.setApplicationEventPublisher(null); - this.authorizationEventPublisher.publishAuthorizationSuccess(mock(AuthorizationDecision.class)); - this.authorizationEventPublisher.publishAuthorizationFailure(mock(AuthorizationDecision.class)); - verify(this.applicationEventPublisher, never()).publishEvent(any()); - } - -} diff --git a/core/src/test/java/org/springframework/security/authorization/SpringAuthorizationEventPublisherTests.java b/core/src/test/java/org/springframework/security/authorization/SpringAuthorizationEventPublisherTests.java new file mode 100644 index 0000000000..cc56d8477e --- /dev/null +++ b/core/src/test/java/org/springframework/security/authorization/SpringAuthorizationEventPublisherTests.java @@ -0,0 +1,67 @@ +/* + * 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 org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.security.authentication.TestAuthentication; +import org.springframework.security.authorization.event.AuthorizationDeniedEvent; +import org.springframework.security.core.Authentication; + +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +/** + * Tests for {@link SpringAuthorizationEventPublisher} + * + * @author Parikshit Dutta + */ +public class SpringAuthorizationEventPublisherTests { + + Supplier authentication = () -> TestAuthentication.authenticatedUser(); + + ApplicationEventPublisher applicationEventPublisher; + + SpringAuthorizationEventPublisher authorizationEventPublisher; + + @BeforeEach + public void init() { + this.applicationEventPublisher = mock(ApplicationEventPublisher.class); + this.authorizationEventPublisher = new SpringAuthorizationEventPublisher(this.applicationEventPublisher); + } + + @Test + public void testAuthenticationSuccessIsNotPublished() { + AuthorizationDecision decision = new AuthorizationDecision(true); + this.authorizationEventPublisher.publishAuthorizationEvent(this.authentication, mock(Object.class), decision); + verifyNoInteractions(this.applicationEventPublisher); + } + + @Test + public void testAuthenticationFailureIsPublished() { + AuthorizationDecision decision = new AuthorizationDecision(false); + this.authorizationEventPublisher.publishAuthorizationEvent(this.authentication, mock(Object.class), decision); + verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationDeniedEvent.class)); + } + +} diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java index 2a129cb56a..63f531d3c5 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -16,11 +16,20 @@ package org.springframework.security.authorization.method; +import java.util.function.Supplier; + import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; import org.springframework.aop.Pointcut; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.authorization.AuthenticatedAuthorizationManager; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -66,4 +75,32 @@ public class AuthorizationManagerAfterMethodInterceptorTests { any(MethodInvocationResult.class)); } + @Test + public void configureWhenAuthorizationEventPublisherIsNullThenIllegalArgument() { + AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor( + Pointcut.TRUE, AuthenticatedAuthorizationManager.authenticated()); + assertThatIllegalArgumentException().isThrownBy(() -> advice.setAuthorizationEventPublisher(null)) + .withMessage("eventPublisher cannot be null"); + } + + @Test + public void invokeWhenAuthorizationEventPublisherThenUses() throws Throwable { + AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor( + Pointcut.TRUE, AuthenticatedAuthorizationManager.authenticated()); + AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class); + advice.setAuthorizationEventPublisher(eventPublisher); + + SecurityContext securityContext = new SecurityContextImpl(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER")); + SecurityContextHolder.setContext(securityContext); + + MethodInvocation mockMethodInvocation = mock(MethodInvocation.class); + MethodInvocationResult result = new MethodInvocationResult(mockMethodInvocation, new Object()); + given(mockMethodInvocation.proceed()).willReturn(result.getResult()); + + advice.invoke(mockMethodInvocation); + verify(eventPublisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocationResult.class), + any(AuthorizationDecision.class)); + } + } diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java index 9065e11040..f4a6a62fc4 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -16,13 +16,24 @@ package org.springframework.security.authorization.method; +import java.util.function.Supplier; + import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; import org.springframework.aop.Pointcut; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.authorization.AuthenticatedAuthorizationManager; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -59,4 +70,32 @@ public class AuthorizationManagerBeforeMethodInterceptorTests { mockMethodInvocation); } + @Test + public void configureWhenAuthorizationEventPublisherIsNullThenIllegalArgument() { + AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor( + Pointcut.TRUE, AuthenticatedAuthorizationManager.authenticated()); + assertThatIllegalArgumentException().isThrownBy(() -> advice.setAuthorizationEventPublisher(null)) + .withMessage("eventPublisher cannot be null"); + } + + @Test + public void invokeWhenAuthorizationEventPublisherThenUses() throws Throwable { + AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor( + Pointcut.TRUE, AuthenticatedAuthorizationManager.authenticated()); + AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class); + advice.setAuthorizationEventPublisher(eventPublisher); + + SecurityContext securityContext = new SecurityContextImpl(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER")); + SecurityContextHolder.setContext(securityContext); + + MethodInvocation mockMethodInvocation = mock(MethodInvocation.class); + MethodInvocationResult result = new MethodInvocationResult(mockMethodInvocation, new Object()); + given(mockMethodInvocation.proceed()).willReturn(result.getResult()); + + advice.invoke(mockMethodInvocation); + verify(eventPublisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocation.class), + any(AuthorizationDecision.class)); + } + } diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java b/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java index 19bcbfdc11..25b5b643ea 100644 --- a/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java +++ b/web/src/main/java/org/springframework/security/web/access/intercept/AuthorizationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * 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. @@ -17,14 +17,21 @@ package org.springframework.security.web.access.intercept; import java.io.IOException; +import java.util.function.Supplier; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.authorization.event.AuthorizationDeniedEvent; +import org.springframework.security.authorization.event.AuthorizationGrantedEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.Assert; @@ -41,6 +48,8 @@ public class AuthorizationFilter extends OncePerRequestFilter { private final AuthorizationManager authorizationManager; + private AuthorizationEventPublisher eventPublisher = AuthorizationFilter::noPublish; + /** * Creates an instance. * @param authorizationManager the {@link AuthorizationManager} to use @@ -54,7 +63,11 @@ public class AuthorizationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - this.authorizationManager.verify(this::getAuthentication, request); + AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request); + this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision); + if (decision != null && !decision.isGranted()) { + throw new AccessDeniedException("Access Denied"); + } filterChain.doFilter(request, response); } @@ -67,6 +80,17 @@ public class AuthorizationFilter extends OncePerRequestFilter { return authentication; } + /** + * Use this {@link AuthorizationEventPublisher} to publish + * {@link AuthorizationDeniedEvent}s and {@link AuthorizationGrantedEvent}s. + * @param eventPublisher the {@link ApplicationEventPublisher} to use + * @since 5.7 + */ + public void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) { + Assert.notNull(eventPublisher, "eventPublisher cannot be null"); + this.eventPublisher = eventPublisher; + } + /** * Gets the {@link AuthorizationManager} used by this filter * @return the {@link AuthorizationManager} @@ -75,4 +99,9 @@ public class AuthorizationFilter extends OncePerRequestFilter { return this.authorizationManager; } + private static void noPublish(Supplier authentication, T object, + AuthorizationDecision decision) { + + } + } diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java b/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java index f751658788..5a7266964b 100644 --- a/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java +++ b/web/src/main/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManager.java @@ -28,7 +28,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -49,8 +48,6 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho private final Map> mappings; - private AuthorizationEventPublisher authorizationEventPublisher; - private RequestMatcherDelegatingAuthorizationManager( Map> mappings) { Assert.notEmpty(mappings, "mappings cannot be empty"); @@ -81,36 +78,14 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager)); } - AuthorizationDecision authorizationDecision = manager.check(authentication, + return manager.check(authentication, new RequestAuthorizationContext(request, matchResult.getVariables())); - publishAuthorizationEvent(authorizationDecision); - return authorizationDecision; } } this.logger.trace("Abstaining since did not find matching RequestMatcher"); return null; } - private void publishAuthorizationEvent(AuthorizationDecision authorizationDecision) { - if (this.authorizationEventPublisher != null) { - if (authorizationDecision.isGranted()) { - this.authorizationEventPublisher.publishAuthorizationSuccess(authorizationDecision); - } - else { - this.authorizationEventPublisher.publishAuthorizationFailure(authorizationDecision); - } - } - } - - /** - * Set implementation of an {@link AuthorizationEventPublisher} - * @param authorizationEventPublisher - */ - public void setAuthorizationEventPublisher(AuthorizationEventPublisher authorizationEventPublisher) { - Assert.notNull(authorizationEventPublisher, "AuthorizationEventPublisher cannot be null"); - this.authorizationEventPublisher = authorizationEventPublisher; - } - /** * Creates a builder for {@link RequestMatcherDelegatingAuthorizationManager}. * @return the new {@link Builder} instance diff --git a/web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java b/web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java index 0923605216..a271b43947 100644 --- a/web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/access/intercept/AuthorizationFilterTests.java @@ -31,6 +31,8 @@ import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.AuthenticatedAuthorizationManager; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -39,8 +41,10 @@ import org.springframework.security.core.context.SecurityContextImpl; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -61,6 +65,8 @@ public class AuthorizationFilterTests { @Test public void filterWhenAuthorizationManagerVerifyPassesThenNextFilter() throws Exception { AuthorizationManager mockAuthorizationManager = mock(AuthorizationManager.class); + given(mockAuthorizationManager.check(any(Supplier.class), any(HttpServletRequest.class))) + .willReturn(new AuthorizationDecision(true)); AuthorizationFilter filter = new AuthorizationFilter(mockAuthorizationManager); TestingAuthenticationToken authenticationToken = new TestingAuthenticationToken("user", "password"); @@ -75,7 +81,7 @@ public class AuthorizationFilterTests { filter.doFilter(mockRequest, mockResponse, mockFilterChain); ArgumentCaptor> authenticationCaptor = ArgumentCaptor.forClass(Supplier.class); - verify(mockAuthorizationManager).verify(authenticationCaptor.capture(), eq(mockRequest)); + verify(mockAuthorizationManager).check(authenticationCaptor.capture(), eq(mockRequest)); Supplier authentication = authenticationCaptor.getValue(); assertThat(authentication.get()).isEqualTo(authenticationToken); @@ -96,7 +102,7 @@ public class AuthorizationFilterTests { MockHttpServletResponse mockResponse = new MockHttpServletResponse(); FilterChain mockFilterChain = mock(FilterChain.class); - willThrow(new AccessDeniedException("Access Denied")).given(mockAuthorizationManager).verify(any(), + willThrow(new AccessDeniedException("Access Denied")).given(mockAuthorizationManager).check(any(), eq(mockRequest)); assertThatExceptionOfType(AccessDeniedException.class) @@ -104,7 +110,7 @@ public class AuthorizationFilterTests { .withMessage("Access Denied"); ArgumentCaptor> authenticationCaptor = ArgumentCaptor.forClass(Supplier.class); - verify(mockAuthorizationManager).verify(authenticationCaptor.capture(), eq(mockRequest)); + verify(mockAuthorizationManager).check(authenticationCaptor.capture(), eq(mockRequest)); Supplier authentication = authenticationCaptor.getValue(); assertThat(authentication.get()).isEqualTo(authenticationToken); @@ -132,4 +138,31 @@ public class AuthorizationFilterTests { assertThat(authorizationFilter.getAuthorizationManager()).isSameAs(authorizationManager); } + @Test + public void configureWhenAuthorizationEventPublisherIsNullThenIllegalArgument() { + AuthorizationManager authorizationManager = mock(AuthorizationManager.class); + AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager); + assertThatIllegalArgumentException().isThrownBy(() -> authorizationFilter.setAuthorizationEventPublisher(null)) + .withMessage("eventPublisher cannot be null"); + } + + @Test + public void doFilterWhenAuthorizationEventPublisherThenUses() throws Exception { + AuthorizationFilter authorizationFilter = new AuthorizationFilter( + AuthenticatedAuthorizationManager.authenticated()); + MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path"); + MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + FilterChain mockFilterChain = mock(FilterChain.class); + + SecurityContext securityContext = new SecurityContextImpl(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER")); + SecurityContextHolder.setContext(securityContext); + + AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class); + authorizationFilter.setAuthorizationEventPublisher(eventPublisher); + authorizationFilter.doFilter(mockRequest, mockResponse, mockFilterChain); + verify(eventPublisher).publishAuthorizationEvent(any(Supplier.class), any(HttpServletRequest.class), + any(AuthorizationDecision.class)); + } + } diff --git a/web/src/test/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManagerTests.java b/web/src/test/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManagerTests.java index 44d49e2789..a67c1740df 100644 --- a/web/src/test/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManagerTests.java +++ b/web/src/test/java/org/springframework/security/web/access/intercept/RequestMatcherDelegatingAuthorizationManagerTests.java @@ -24,15 +24,12 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.AuthorityAuthorizationManager; import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.core.Authentication; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.util.matcher.AnyRequestMatcher; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; /** * Tests for {@link RequestMatcherDelegatingAuthorizationManager}. @@ -126,40 +123,4 @@ public class RequestMatcherDelegatingAuthorizationManagerTests { .withMessage("mappingsConsumer cannot be null"); } - @Test - public void testAuthorizationEventPublisherIsNotNull() { - RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder() - .add(new MvcRequestMatcher(null, "/grant"), (a, o) -> new AuthorizationDecision(true)).build(); - assertThatIllegalArgumentException().isThrownBy(() -> manager.setAuthorizationEventPublisher(null)) - .withMessage("AuthorizationEventPublisher cannot be null"); - } - - @Test - public void testAuthorizationSuccessEventWhenAuthorizationGranted() { - RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder() - .add(new MvcRequestMatcher(null, "/grant"), (a, o) -> new AuthorizationDecision(true)).build(); - - AuthorizationEventPublisher authorizationEventPublisher = mock(AuthorizationEventPublisher.class); - manager.setAuthorizationEventPublisher(authorizationEventPublisher); - - Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); - - AuthorizationDecision grant = manager.check(authentication, new MockHttpServletRequest(null, "/grant")); - verify(authorizationEventPublisher).publishAuthorizationSuccess(grant); - } - - @Test - public void testAuthorizationFailureEventWhenAuthorizationNotGranted() { - RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder() - .add(new MvcRequestMatcher(null, "/deny"), (a, o) -> new AuthorizationDecision(false)).build(); - - AuthorizationEventPublisher authorizationEventPublisher = mock(AuthorizationEventPublisher.class); - manager.setAuthorizationEventPublisher(authorizationEventPublisher); - - Supplier authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER"); - - AuthorizationDecision grant = manager.check(authentication, new MockHttpServletRequest(null, "/deny")); - verify(authorizationEventPublisher).publishAuthorizationFailure(grant); - } - }