From 2fbbcc4bd006dff49bc97b948ae264e0a7978d86 Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Tue, 9 Apr 2024 10:56:40 -0300 Subject: [PATCH] Polish Method Authorization Denied Handling - Renamed @AuthorizationDeniedHandler to @HandleAuthorizationDenied - Merged the post processor interface into MethodAuthorizationDeniedHandler , it now has two methods handleDeniedInvocation and handleDeniedInvocationResult - @HandleAuthorizationDenied now handles AuthorizationDeniedException thrown from the method Issue gh-14601 --- ...erringObservationAuthorizationManager.java | 17 +--- ...servationReactiveAuthorizationManager.java | 19 ++-- .../configuration/MethodSecurityService.java | 80 ++++++++++------ .../MethodSecurityServiceImpl.java | 7 +- ...ePostMethodSecurityConfigurationTests.java | 49 ++++------ ...ctiveMethodSecurityConfigurationTests.java | 27 +++--- ...ctiveMethodSecurityConfigurationTests.java | 23 ++--- .../ReactiveMethodSecurityService.java | 70 ++++++++------ .../ReactiveMethodSecurityServiceImpl.java | 7 +- .../UserRecordWithEmailProtected.java | 18 +++- .../ObservationAuthorizationManager.java | 19 ++-- ...servationReactiveAuthorizationManager.java | 19 ++-- ...rizationManagerAfterMethodInterceptor.java | 32 ++++--- ...ManagerAfterReactiveMethodInterceptor.java | 55 +++++++---- ...izationManagerBeforeMethodInterceptor.java | 25 ++++- ...anagerBeforeReactiveMethodInterceptor.java | 38 +++----- ...er.java => HandleAuthorizationDenied.java} | 22 ++--- .../MethodAuthorizationDeniedHandler.java | 25 ++++- ...ethodAuthorizationDeniedPostProcessor.java | 46 --------- .../PostAuthorizeAuthorizationManager.java | 14 ++- .../PostAuthorizeExpressionAttribute.java | 12 +-- ...tAuthorizeExpressionAttributeRegistry.java | 42 ++++---- ...AuthorizeReactiveAuthorizationManager.java | 14 ++- .../PreAuthorizeAuthorizationManager.java | 6 +- ...eAuthorizeExpressionAttributeRegistry.java | 6 +- ...AuthorizeReactiveAuthorizationManager.java | 4 +- ...owingMethodAuthorizationDeniedHandler.java | 15 ++- ...ethodAuthorizationDeniedPostProcessor.java | 39 -------- ...erAfterReactiveMethodInterceptorTests.java | 12 +-- ...rBeforeReactiveMethodInterceptorTests.java | 7 +- .../authorization/method-security.adoc | 96 +++++++++++-------- 31 files changed, 425 insertions(+), 440 deletions(-) rename core/src/main/java/org/springframework/security/authorization/method/{AuthorizationDeniedHandler.java => HandleAuthorizationDenied.java} (68%) delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedPostProcessor.java delete mode 100644 core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedPostProcessor.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java index 05068d98a9..3328531606 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java @@ -27,22 +27,18 @@ import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ObservationAuthorizationManager; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor; import org.springframework.security.core.Authentication; import org.springframework.util.function.SingletonSupplier; final class DeferringObservationAuthorizationManager - implements AuthorizationManager, MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { + implements AuthorizationManager, MethodAuthorizationDeniedHandler { private final Supplier> delegate; private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); - DeferringObservationAuthorizationManager(ObjectProvider provider, AuthorizationManager delegate) { this.delegate = SingletonSupplier.of(() -> { @@ -55,9 +51,6 @@ final class DeferringObservationAuthorizationManager if (delegate instanceof MethodAuthorizationDeniedHandler h) { this.handler = h; } - if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) { - this.postProcessor = p; - } } @Override @@ -66,14 +59,14 @@ final class DeferringObservationAuthorizationManager } @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - return this.handler.handle(methodInvocation, authorizationResult); + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult); } @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { - return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult); + return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java index 7600925bf2..8b028a7077 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java @@ -28,22 +28,18 @@ import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.ObservationReactiveAuthorizationManager; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor; import org.springframework.security.core.Authentication; import org.springframework.util.function.SingletonSupplier; -final class DeferringObservationReactiveAuthorizationManager implements ReactiveAuthorizationManager, - MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { +final class DeferringObservationReactiveAuthorizationManager + implements ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { private final Supplier> delegate; private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); - DeferringObservationReactiveAuthorizationManager(ObjectProvider provider, ReactiveAuthorizationManager delegate) { this.delegate = SingletonSupplier.of(() -> { @@ -56,9 +52,6 @@ final class DeferringObservationReactiveAuthorizationManager implements React if (delegate instanceof MethodAuthorizationDeniedHandler h) { this.handler = h; } - if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) { - this.postProcessor = p; - } } @Override @@ -67,14 +60,14 @@ final class DeferringObservationReactiveAuthorizationManager implements React } @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - return this.handler.handle(methodInvocation, authorizationResult); + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult); } @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { - return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult); + return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult); } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java index 8557b72f20..3fff7b52e4 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java @@ -39,10 +39,9 @@ import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.authorization.AuthorizationResult; -import org.springframework.security.authorization.method.AuthorizationDeniedHandler; import org.springframework.security.authorization.method.AuthorizeReturnObject; +import org.springframework.security.authorization.method.HandleAuthorizationDenied; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -129,32 +128,32 @@ public interface MethodSecurityService { void repeatedAnnotations(); @PreAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class) + @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class) String preAuthorizeGetCardNumberIfAdmin(String cardNumber); @PreAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class) + @HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class) String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber); @PreAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class) + @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class) String preAuthorizeThrowAccessDeniedManually(); @PostAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class) String postAuthorizeGetCardNumberIfAdmin(String cardNumber); @PostAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class) String postAuthorizeThrowAccessDeniedManually(); @PreAuthorize("denyAll()") @Mask("methodmask") - @AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) String preAuthorizeDeniedMethodWithMaskAnnotation(); @PreAuthorize("denyAll()") - @AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) String preAuthorizeDeniedMethodWithNoMaskAnnotation(); @NullDenied(role = "ADMIN") @@ -162,40 +161,39 @@ public interface MethodSecurityService { @PostAuthorize("denyAll()") @Mask("methodmask") - @AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) String postAuthorizeDeniedMethodWithMaskAnnotation(); @PostAuthorize("denyAll()") - @AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) String postAuthorizeDeniedMethodWithNoMaskAnnotation(); @PreAuthorize("hasRole('ADMIN')") @Mask(expression = "@myMasker.getMask()") - @AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) String preAuthorizeWithMaskAnnotationUsingBean(); @PostAuthorize("hasRole('ADMIN')") @Mask(expression = "@myMasker.getMask(returnObject)") - @AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) String postAuthorizeWithMaskAnnotationUsingBean(); @AuthorizeReturnObject UserRecordWithEmailProtected getUserRecordWithEmailProtected(); @PreAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(handlerClass = UserFallbackDeniedHandler.class) + @HandleAuthorizationDenied(handlerClass = UserFallbackDeniedHandler.class) UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized(); @PreAuthorize("@authz.checkResult(#result)") @PostAuthorize("@authz.checkResult(!#result)") - @AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class, - postProcessorClass = MethodAuthorizationDeniedPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class) String checkCustomResult(boolean result); class StarMaskingHandler implements MethodAuthorizationDeniedHandler { @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) { return "***"; } @@ -204,8 +202,8 @@ public interface MethodSecurityService { class StartMaskingHandlerChild extends StarMaskingHandler { @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { - return super.handle(methodInvocation, result) + "-child"; + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) { + return super.handleDeniedInvocation(methodInvocation, result) + "-child"; } } @@ -218,7 +216,6 @@ public interface MethodSecurityService { this.maskValueResolver = new MaskValueResolver(context); } - @Override public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class); if (mask == null) { @@ -227,9 +224,15 @@ public interface MethodSecurityService { return this.maskValueResolver.resolveValue(mask, methodInvocation, null); } + @Override + public Object handleDeniedInvocation(MethodInvocation methodInvocation, + AuthorizationResult authorizationResult) { + return handle(methodInvocation, authorizationResult); + } + } - class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedPostProcessor { + class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedHandler { MaskValueResolver maskValueResolver; @@ -238,7 +241,16 @@ public interface MethodSecurityService { } @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocation(MethodInvocation mi, AuthorizationResult authorizationResult) { + Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); + if (mask == null) { + mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class); + } + return this.maskValueResolver.resolveValue(mask, mi, null); + } + + @Override + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { MethodInvocation mi = methodInvocationResult.getMethodInvocation(); Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); @@ -274,31 +286,38 @@ public interface MethodSecurityService { } - class PostMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { + class PostMaskingPostProcessor implements MethodAuthorizationDeniedHandler { @Override - public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, + AuthorizationResult authorizationResult) { return "***"; } } - class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { + class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedHandler { static String MASK = "****-****-****-"; @Override - public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, + AuthorizationResult authorizationResult) { + return "***"; + } + + @Override + public Object handleDeniedInvocationResult(MethodInvocationResult contextObject, AuthorizationResult result) { String cardNumber = (String) contextObject.getResult(); return MASK + cardNumber.substring(cardNumber.length() - 4); } } - class NullPostProcessor implements MethodAuthorizationDeniedPostProcessor { + class NullPostProcessor implements MethodAuthorizationDeniedHandler { @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { return null; } @@ -320,7 +339,7 @@ public interface MethodSecurityService { @Retention(RetentionPolicy.RUNTIME) @Inherited @PostAuthorize("hasRole('{role}')") - @AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = NullPostProcessor.class) @interface NullDenied { String role(); @@ -333,7 +352,8 @@ public interface MethodSecurityService { "Protected"); @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, + AuthorizationResult authorizationResult) { return FALLBACK; } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java index 4fd5b7fe1a..6bf15c304d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java @@ -18,7 +18,8 @@ package org.springframework.security.config.annotation.method.configuration; import java.util.List; -import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -144,12 +145,12 @@ public class MethodSecurityServiceImpl implements MethodSecurityService { @Override public String preAuthorizeThrowAccessDeniedManually() { - throw new AccessDeniedException("Access Denied"); + throw new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)); } @Override public String postAuthorizeThrowAccessDeniedManually() { - throw new AccessDeniedException("Access Denied"); + throw new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)); } @Override 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 7910b16de5..69a42a7e47 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 @@ -67,7 +67,6 @@ import org.springframework.security.authorization.method.AuthorizationIntercepto import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.authorization.method.AuthorizeReturnObject; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.PrePostTemplateDefaults; import org.springframework.security.config.Customizer; @@ -92,10 +91,10 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; /** * Tests for {@link PrePostMethodSecurityConfiguration}. @@ -783,12 +782,21 @@ public class PrePostMethodSecurityConfigurationTests { @Test @WithMockUser(roles = "ADMIN") - void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenNotHandled() { + void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenHandled() { this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class) .autowire(); MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(service::preAuthorizeThrowAccessDeniedManually); + assertThat(service.preAuthorizeThrowAccessDeniedManually()).isEqualTo("***"); + } + + @Test + @WithMockUser(roles = "ADMIN") + void postAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPostAuthorizeThenHandled() { + this.spring + .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.PostMaskingPostProcessor.class) + .autowire(); + MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); + assertThat(service.postAuthorizeThrowAccessDeniedManually()).isEqualTo("***"); } @Test @@ -813,17 +821,6 @@ public class PrePostMethodSecurityConfigurationTests { assertThat(result).isEqualTo("classmask"); } - @Test - @WithMockUser(roles = "ADMIN") - void postAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPostAuthorizeThenNotHandled() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.PostMaskingPostProcessor.class) - .autowire(); - MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(service::postAuthorizeThrowAccessDeniedManually); - } - @Test @WithMockUser void postAuthorizeWhenNullDeniedMetaAnnotationThanWorks() { @@ -938,14 +935,13 @@ public class PrePostMethodSecurityConfigurationTests { MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class); MethodAuthorizationDeniedHandler handler = this.spring.getContext() .getBean(MethodAuthorizationDeniedHandler.class); - MethodAuthorizationDeniedPostProcessor postProcessor = this.spring.getContext() - .getBean(MethodAuthorizationDeniedPostProcessor.class); assertThat(service.checkCustomResult(false)).isNull(); - verify(handler).handle(any(), any(Authz.AuthzResult.class)); - verifyNoInteractions(postProcessor); + verify(handler).handleDeniedInvocation(any(), any(Authz.AuthzResult.class)); + verify(handler, never()).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class)); + clearInvocations(handler); assertThat(service.checkCustomResult(true)).isNull(); - verify(postProcessor).postProcessResult(any(), any(Authz.AuthzResult.class)); - verifyNoMoreInteractions(handler); + verify(handler).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class)); + verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class)); } private static Consumer disallowBeanOverriding() { @@ -1477,18 +1473,11 @@ public class PrePostMethodSecurityConfigurationTests { MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class); - MethodAuthorizationDeniedPostProcessor postProcessor = mock(MethodAuthorizationDeniedPostProcessor.class); - @Bean MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() { return this.handler; } - @Bean - MethodAuthorizationDeniedPostProcessor methodAuthorizationDeniedPostProcessor() { - return this.postProcessor; - } - } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java index b8abf34519..5fe335870d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java @@ -22,7 +22,6 @@ import reactor.test.StepVerifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.AccessDeniedException; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; @@ -73,18 +72,6 @@ public class PrePostReactiveMethodSecurityConfigurationTests { .verifyComplete(); } - @Test - @WithMockUser(roles = "ADMIN") - void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenNotHandled() { - this.spring - .register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class) - .autowire(); - ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.preAuthorizeThrowAccessDeniedManually()) - .expectError(AccessDeniedException.class) - .verify(); - } - @Test @WithMockUser void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() { @@ -119,9 +106,17 @@ public class PrePostReactiveMethodSecurityConfigurationTests { ReactiveMethodSecurityService.PostMaskingPostProcessor.class) .autowire(); ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); - StepVerifier.create(service.postAuthorizeThrowAccessDeniedManually()) - .expectError(AccessDeniedException.class) - .verify(); + StepVerifier.create(service.postAuthorizeThrowAccessDeniedManually()).expectNext("***").verifyComplete(); + } + + @Test + @WithMockUser(roles = "ADMIN") + void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenHandled() { + this.spring + .register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class) + .autowire(); + ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); + StepVerifier.create(service.preAuthorizeThrowAccessDeniedManually()).expectNext("***").verifyComplete(); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java index 1a2fc446a7..cf845ebb61 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java @@ -48,7 +48,6 @@ import org.springframework.security.authorization.method.AuthorizationAdvisorPro import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor; import org.springframework.security.authorization.method.AuthorizeReturnObject; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; import org.springframework.security.config.Customizer; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.test.SpringTestContext; @@ -60,10 +59,10 @@ import org.springframework.security.test.context.support.WithMockUser; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; /** * @author Tadaya Tsuyukubo @@ -227,14 +226,13 @@ public class ReactiveMethodSecurityConfigurationTests { ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); MethodAuthorizationDeniedHandler handler = this.spring.getContext() .getBean(MethodAuthorizationDeniedHandler.class); - MethodAuthorizationDeniedPostProcessor postProcessor = this.spring.getContext() - .getBean(MethodAuthorizationDeniedPostProcessor.class); assertThat(service.checkCustomResult(false).block()).isNull(); - verify(handler).handle(any(), any(Authz.AuthzResult.class)); - verifyNoInteractions(postProcessor); + verify(handler).handleDeniedInvocation(any(), any(Authz.AuthzResult.class)); + verify(handler, never()).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class)); + clearInvocations(handler); assertThat(service.checkCustomResult(true).block()).isNull(); - verify(postProcessor).postProcessResult(any(), any(Authz.AuthzResult.class)); - verifyNoMoreInteractions(handler); + verify(handler).handleDeniedInvocationResult(any(), any(Authz.AuthzResult.class)); + verify(handler, never()).handleDeniedInvocation(any(), any(Authz.AuthzResult.class)); } private static Consumer authorities(String... authorities) { @@ -383,18 +381,11 @@ public class ReactiveMethodSecurityConfigurationTests { MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class); - MethodAuthorizationDeniedPostProcessor postProcessor = mock(MethodAuthorizationDeniedPostProcessor.class); - @Bean MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() { return this.handler; } - @Bean - MethodAuthorizationDeniedPostProcessor methodAuthorizationDeniedPostProcessor() { - return this.postProcessor; - } - } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java index 1bdef8f246..000dcb386a 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java @@ -33,9 +33,8 @@ import org.springframework.security.access.expression.method.DefaultMethodSecuri import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authorization.AuthorizationResult; -import org.springframework.security.authorization.method.AuthorizationDeniedHandler; +import org.springframework.security.authorization.method.HandleAuthorizationDenied; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; @@ -47,32 +46,32 @@ import org.springframework.util.StringUtils; public interface ReactiveMethodSecurityService { @PreAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class) + @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class) Mono preAuthorizeGetCardNumberIfAdmin(String cardNumber); @PreAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class) + @HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class) Mono preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber); @PreAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class) + @HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class) Mono preAuthorizeThrowAccessDeniedManually(); @PostAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class) Mono postAuthorizeGetCardNumberIfAdmin(String cardNumber); @PostAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class) Mono postAuthorizeThrowAccessDeniedManually(); @PreAuthorize("denyAll()") @Mask("methodmask") - @AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) Mono preAuthorizeDeniedMethodWithMaskAnnotation(); @PreAuthorize("denyAll()") - @AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) Mono preAuthorizeDeniedMethodWithNoMaskAnnotation(); @NullDenied(role = "ADMIN") @@ -80,33 +79,32 @@ public interface ReactiveMethodSecurityService { @PostAuthorize("denyAll()") @Mask("methodmask") - @AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) Mono postAuthorizeDeniedMethodWithMaskAnnotation(); @PostAuthorize("denyAll()") - @AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) Mono postAuthorizeDeniedMethodWithNoMaskAnnotation(); @PreAuthorize("hasRole('ADMIN')") @Mask(expression = "@myMasker.getMask()") - @AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class) Mono preAuthorizeWithMaskAnnotationUsingBean(); @PostAuthorize("hasRole('ADMIN')") @Mask(expression = "@myMasker.getMask(returnObject)") - @AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class) Mono postAuthorizeWithMaskAnnotationUsingBean(); @PreAuthorize("@authz.checkReactiveResult(#result)") @PostAuthorize("@authz.checkReactiveResult(!#result)") - @AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class, - postProcessorClass = MethodAuthorizationDeniedPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class) Mono checkCustomResult(boolean result); class StarMaskingHandler implements MethodAuthorizationDeniedHandler { @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) { return "***"; } @@ -115,8 +113,8 @@ public interface ReactiveMethodSecurityService { class StartMaskingHandlerChild extends StarMaskingHandler { @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { - return super.handle(methodInvocation, result) + "-child"; + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) { + return super.handleDeniedInvocation(methodInvocation, result) + "-child"; } } @@ -130,7 +128,7 @@ public interface ReactiveMethodSecurityService { } @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult result) { Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class); if (mask == null) { mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod().getDeclaringClass(), Mask.class); @@ -140,7 +138,7 @@ public interface ReactiveMethodSecurityService { } - class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedPostProcessor { + class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedHandler { MaskValueResolver maskValueResolver; @@ -149,7 +147,16 @@ public interface ReactiveMethodSecurityService { } @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocation(MethodInvocation mi, AuthorizationResult authorizationResult) { + Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); + if (mask == null) { + mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class); + } + return this.maskValueResolver.resolveValue(mask, mi, null); + } + + @Override + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { MethodInvocation mi = methodInvocationResult.getMethodInvocation(); Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); @@ -185,31 +192,38 @@ public interface ReactiveMethodSecurityService { } - class PostMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { + class PostMaskingPostProcessor implements MethodAuthorizationDeniedHandler { @Override - public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, + AuthorizationResult authorizationResult) { return "***"; } } - class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { + class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedHandler { static String MASK = "****-****-****-"; @Override - public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, + AuthorizationResult authorizationResult) { + return "***"; + } + + @Override + public Object handleDeniedInvocationResult(MethodInvocationResult contextObject, AuthorizationResult result) { String cardNumber = (String) contextObject.getResult(); return MASK + cardNumber.substring(cardNumber.length() - 4); } } - class NullPostProcessor implements MethodAuthorizationDeniedPostProcessor { + class NullPostProcessor implements MethodAuthorizationDeniedHandler { @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { return null; } @@ -231,7 +245,7 @@ public interface ReactiveMethodSecurityService { @Retention(RetentionPolicy.RUNTIME) @Inherited @PostAuthorize("hasRole('{value}')") - @AuthorizationDeniedHandler(postProcessorClass = NullPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = NullPostProcessor.class) @interface NullDenied { String role(); diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java index ce6a0204b6..acf50eb113 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java @@ -18,7 +18,8 @@ package org.springframework.security.config.annotation.method.configuration; import reactor.core.publisher.Mono; -import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationDeniedException; public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurityService { @@ -34,7 +35,7 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity @Override public Mono preAuthorizeThrowAccessDeniedManually() { - return Mono.error(new AccessDeniedException("Access Denied")); + return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false))); } @Override @@ -44,7 +45,7 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity @Override public Mono postAuthorizeThrowAccessDeniedManually() { - return Mono.error(new AccessDeniedException("Access Denied")); + return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false))); } @Override diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/UserRecordWithEmailProtected.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/UserRecordWithEmailProtected.java index b3b2e2075a..de3e9f72f2 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/UserRecordWithEmailProtected.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/UserRecordWithEmailProtected.java @@ -16,10 +16,12 @@ package org.springframework.security.config.annotation.method.configuration; +import org.aopalliance.intercept.MethodInvocation; + import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.authorization.AuthorizationResult; -import org.springframework.security.authorization.method.AuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; +import org.springframework.security.authorization.method.HandleAuthorizationDenied; +import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; import org.springframework.security.authorization.method.MethodInvocationResult; public class UserRecordWithEmailProtected { @@ -38,15 +40,21 @@ public class UserRecordWithEmailProtected { } @PostAuthorize("hasRole('ADMIN')") - @AuthorizationDeniedHandler(postProcessorClass = EmailMaskingPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = EmailMaskingPostProcessor.class) public String email() { return this.email; } - public static class EmailMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { + public static class EmailMaskingPostProcessor implements MethodAuthorizationDeniedHandler { @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocation(MethodInvocation methodInvocation, + AuthorizationResult authorizationResult) { + return "***"; + } + + @Override + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { String email = (String) methodInvocationResult.getResult(); return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*"); diff --git a/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java index 3f0050b0c4..00deb1c035 100644 --- a/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java @@ -28,10 +28,8 @@ import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor; import org.springframework.security.core.Authentication; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.util.Assert; @@ -42,8 +40,8 @@ import org.springframework.util.Assert; * @author Josh Cummings * @since 6.0 */ -public final class ObservationAuthorizationManager implements AuthorizationManager, MessageSourceAware, - MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { +public final class ObservationAuthorizationManager + implements AuthorizationManager, MessageSourceAware, MethodAuthorizationDeniedHandler { private final ObservationRegistry registry; @@ -55,17 +53,12 @@ public final class ObservationAuthorizationManager implements AuthorizationMa private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); - public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager delegate) { this.registry = registry; this.delegate = delegate; if (delegate instanceof MethodAuthorizationDeniedHandler h) { this.handler = h; } - if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) { - this.postProcessor = p; - } } @Override @@ -116,14 +109,14 @@ public final class ObservationAuthorizationManager implements AuthorizationMa } @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - return this.handler.handle(methodInvocation, authorizationResult); + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult); } @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { - return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult); + return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult); } } diff --git a/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java index d84aa7e996..d6e7a2b2c6 100644 --- a/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java @@ -25,10 +25,8 @@ import reactor.core.publisher.Mono; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler; -import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; @@ -38,8 +36,8 @@ import org.springframework.util.Assert; * @author Josh Cummings * @since 6.0 */ -public final class ObservationReactiveAuthorizationManager implements ReactiveAuthorizationManager, - MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor { +public final class ObservationReactiveAuthorizationManager + implements ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { private final ObservationRegistry registry; @@ -49,8 +47,6 @@ public final class ObservationReactiveAuthorizationManager implements Reactiv private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler(); - private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); - public ObservationReactiveAuthorizationManager(ObservationRegistry registry, ReactiveAuthorizationManager delegate) { this.registry = registry; @@ -58,9 +54,6 @@ public final class ObservationReactiveAuthorizationManager implements Reactiv if (delegate instanceof MethodAuthorizationDeniedHandler h) { this.handler = h; } - if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) { - this.postProcessor = p; - } } @Override @@ -99,14 +92,14 @@ public final class ObservationReactiveAuthorizationManager implements Reactiv } @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { - return this.handler.handle(methodInvocation, authorizationResult); + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + return this.handler.handleDeniedInvocation(methodInvocation, authorizationResult); } @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { - return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult); + return this.handler.handleDeniedInvocationResult(methodInvocationResult, authorizationResult); } } 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 ef37e58d5f..63448a6472 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 @@ -33,7 +33,6 @@ import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; @@ -57,7 +56,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori private final AuthorizationManager authorizationManager; - private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); + private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler(); private int order; @@ -119,7 +118,16 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori */ @Override public Object invoke(MethodInvocation mi) throws Throwable { - Object result = mi.proceed(); + Object result; + try { + result = mi.proceed(); + } + catch (AuthorizationDeniedException ex) { + if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { + return handler.handleDeniedInvocation(mi, ex); + } + return this.defaultHandler.handleDeniedInvocation(mi, ex); + } return attemptAuthorization(mi, result); } @@ -174,28 +182,22 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori private Object attemptAuthorization(MethodInvocation mi, Object result) { this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi)); MethodInvocationResult object = new MethodInvocationResult(mi, result); - AuthorizationDecision decision; - try { - decision = this.authorizationManager.check(this::getAuthentication, object); - } - catch (AuthorizationDeniedException denied) { - return postProcess(object, denied); - } + AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, object); this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, object, decision); if (decision != null && !decision.isGranted()) { this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager " + this.authorizationManager + " and decision " + decision)); - return postProcess(object, decision); + return handlePostInvocationDenied(object, decision); } this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi)); return result; } - private Object postProcess(MethodInvocationResult mi, AuthorizationResult decision) { - if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) { - return postProcessableDecision.postProcessResult(mi, decision); + private Object handlePostInvocationDenied(MethodInvocationResult mi, AuthorizationDecision decision) { + if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler deniedHandler) { + return deniedHandler.handleDeniedInvocationResult(mi, decision); } - return this.defaultPostProcessor.postProcessResult(mi, decision); + return this.defaultHandler.handleDeniedInvocationResult(mi, decision); } private Authentication getAuthentication() { diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java index d996242115..fa53945a69 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java @@ -26,6 +26,7 @@ import org.aopalliance.intercept.MethodInvocation; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.publisher.Signal; import org.springframework.aop.Pointcut; import org.springframework.core.KotlinDetector; @@ -60,7 +61,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements private int order = AuthorizationInterceptorsOrder.LAST.getOrder(); - private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); + private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler(); /** * Creates an instance for the {@link PostAuthorize} annotation. @@ -118,27 +119,39 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements + "(for example, a Mono or Flux) or the function must be a Kotlin coroutine " + "in order to support Reactor Context"); Mono authentication = ReactiveAuthenticationUtils.getAuthentication(); - Function> postAuthorize = (result) -> postAuthorize(authentication, mi, result); + Function, Mono> postAuthorize = (signal) -> { + if (signal.isOnComplete()) { + return Mono.empty(); + } + if (!signal.hasError()) { + return postAuthorize(authentication, mi, signal.get()); + } + if (signal.getThrowable() instanceof AuthorizationDeniedException denied) { + return postProcess(denied, mi); + } + return Mono.error(signal.getThrowable()); + }; ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); if (hasFlowReturnType) { if (isSuspendingFunction) { Publisher publisher = ReactiveMethodInvocationUtils.proceed(mi); - return Flux.from(publisher).flatMap(postAuthorize); + return Flux.from(publisher).materialize().flatMap(postAuthorize); } else { Assert.state(adapter != null, () -> "The returnType " + type + " on " + method + " must have a org.springframework.core.ReactiveAdapter registered"); Flux response = Flux.defer(() -> adapter.toPublisher(ReactiveMethodInvocationUtils.proceed(mi))) + .materialize() .flatMap(postAuthorize); return KotlinDelegate.asFlow(response); } } Publisher publisher = ReactiveMethodInvocationUtils.proceed(mi); if (isMultiValue(type, adapter)) { - Flux flux = Flux.from(publisher).flatMap(postAuthorize); + Flux flux = Flux.from(publisher).materialize().flatMap(postAuthorize); return (adapter != null) ? adapter.fromPublisher(flux) : flux; } - Mono mono = Mono.from(publisher).flatMap(postAuthorize); + Mono mono = Mono.from(publisher).materialize().flatMap(postAuthorize); return (adapter != null) ? adapter.fromPublisher(mono) : mono; } @@ -153,17 +166,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements MethodInvocationResult invocationResult = new MethodInvocationResult(mi, result); return this.authorizationManager.check(authentication, invocationResult) .switchIfEmpty(Mono.just(new AuthorizationDecision(false))) - .materialize() - .flatMap((signal) -> { - if (!signal.hasError()) { - AuthorizationDecision decision = signal.get(); - return postProcess(decision, invocationResult); - } - if (signal.getThrowable() instanceof AuthorizationDeniedException denied) { - return postProcess(denied, invocationResult); - } - return Mono.error(signal.getThrowable()); - }); + .flatMap((decision) -> postProcess(decision, invocationResult)); } private Mono postProcess(AuthorizationResult decision, MethodInvocationResult methodInvocationResult) { @@ -171,10 +174,24 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements return Mono.just(methodInvocationResult.getResult()); } return Mono.fromSupplier(() -> { - if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) { - return postProcessableDecision.postProcessResult(methodInvocationResult, decision); + if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { + return handler.handleDeniedInvocationResult(methodInvocationResult, decision); } - return this.defaultPostProcessor.postProcessResult(methodInvocationResult, decision); + return this.defaultHandler.handleDeniedInvocationResult(methodInvocationResult, decision); + }).flatMap((processedResult) -> { + if (Mono.class.isAssignableFrom(processedResult.getClass())) { + return (Mono) processedResult; + } + return Mono.justOrEmpty(processedResult); + }); + } + + private Mono postProcess(AuthorizationResult decision, MethodInvocation methodInvocation) { + return Mono.fromSupplier(() -> { + if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { + return handler.handleDeniedInvocation(methodInvocation, decision); + } + return this.defaultHandler.handleDeniedInvocation(methodInvocation, decision); }).flatMap((processedResult) -> { if (Mono.class.isAssignableFrom(processedResult.getClass())) { return (Mono) processedResult; 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 5d85967faa..cb753e34cd 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 @@ -261,14 +261,33 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author return handle(mi, decision); } this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi)); - return mi.proceed(); + return proceed(mi); + } + + private Object proceed(MethodInvocation mi) throws Throwable { + try { + return mi.proceed(); + } + catch (AuthorizationDeniedException ex) { + if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { + return handler.handleDeniedInvocation(mi, ex); + } + return this.defaultHandler.handleDeniedInvocation(mi, ex); + } + } + + private Object handle(MethodInvocation mi, AuthorizationDeniedException denied) { + if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { + return handler.handleDeniedInvocation(mi, denied); + } + return this.defaultHandler.handleDeniedInvocation(mi, denied); } private Object handle(MethodInvocation mi, AuthorizationResult decision) { if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { - return handler.handle(mi, decision); + return handler.handleDeniedInvocation(mi, decision); } - return this.defaultHandler.handle(mi, decision); + return this.defaultHandler.handleDeniedInvocation(mi, decision); } private Authentication getAuthentication() { diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java index 71a1f00881..ce9f94ae71 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java @@ -142,19 +142,12 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement Mono authentication = ReactiveAuthenticationUtils.getAuthentication(); return this.authorizationManager.check(authentication, mi) .switchIfEmpty(Mono.just(new AuthorizationDecision(false))) - .materialize() - .flatMapMany((signal) -> { - if (!signal.hasError()) { - AuthorizationDecision decision = signal.get(); - if (decision.isGranted()) { - return mapping; - } - return postProcess(decision, mi); + .flatMapMany((decision) -> { + if (decision.isGranted()) { + return mapping.onErrorResume(AuthorizationDeniedException.class, + (deniedEx) -> postProcess(deniedEx, mi)); } - if (signal.getThrowable() instanceof AuthorizationDeniedException denied) { - return postProcess(denied, mi); - } - return Mono.error(signal.getThrowable()); + return postProcess(decision, mi); }); } @@ -162,28 +155,21 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement Mono authentication = ReactiveAuthenticationUtils.getAuthentication(); return this.authorizationManager.check(authentication, mi) .switchIfEmpty(Mono.just(new AuthorizationDecision(false))) - .materialize() - .flatMap((signal) -> { - if (!signal.hasError()) { - AuthorizationDecision decision = signal.get(); - if (decision.isGranted()) { - return mapping; - } - return postProcess(decision, mi); + .flatMap((decision) -> { + if (decision.isGranted()) { + return mapping.onErrorResume(AuthorizationDeniedException.class, + (deniedEx) -> postProcess(deniedEx, mi)); } - if (signal.getThrowable() instanceof AuthorizationDeniedException denied) { - return postProcess(denied, mi); - } - return Mono.error(signal.getThrowable()); + return postProcess(decision, mi); }); } private Mono postProcess(AuthorizationResult decision, MethodInvocation mi) { return Mono.fromSupplier(() -> { if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) { - return handler.handle(mi, decision); + return handler.handleDeniedInvocation(mi, decision); } - return this.defaultHandler.handle(mi, decision); + return this.defaultHandler.handleDeniedInvocation(mi, decision); }).flatMap((result) -> { if (Mono.class.isAssignableFrom(result.getClass())) { return (Mono) result; diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationDeniedHandler.java b/core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java similarity index 68% rename from core/src/main/java/org/springframework/security/authorization/method/AuthorizationDeniedHandler.java rename to core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java index 349c84a5cd..7a28e9324e 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationDeniedHandler.java +++ b/core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java @@ -25,32 +25,26 @@ import java.lang.annotation.Target; /** * Annotation for specifying handling behavior when an authorization denied happens in - * method security + * method security or an + * {@link org.springframework.security.authorization.AuthorizationDeniedException} is + * thrown during method invocation * * @author Marcus da Coregio * @since 6.3 - * @see org.springframework.security.access.prepost.PreAuthorize - * @see org.springframework.security.access.prepost.PostAuthorize + * @see AuthorizationManagerAfterMethodInterceptor + * @see AuthorizationManagerBeforeMethodInterceptor */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented -public @interface AuthorizationDeniedHandler { +public @interface HandleAuthorizationDenied { /** - * The {@link MethodAuthorizationDeniedHandler} used to handle denied authorizations - * from {@link org.springframework.security.access.prepost.PreAuthorize} + * The {@link MethodAuthorizationDeniedHandler} used to handle denied authorization + * results * @return */ Class handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class; - /** - * The {@link MethodAuthorizationDeniedPostProcessor} used to post process denied - * authorizations from - * {@link org.springframework.security.access.prepost.PostAuthorize} - * @return - */ - Class postProcessorClass() default ThrowingMethodAuthorizationDeniedPostProcessor.class; - } diff --git a/core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedHandler.java b/core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedHandler.java index 8ccc797d98..5b059cf0b6 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedHandler.java +++ b/core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedHandler.java @@ -27,13 +27,14 @@ import org.springframework.security.authorization.AuthorizationResult; * @author Marcus da Coregio * @since 6.3 * @see org.springframework.security.access.prepost.PreAuthorize + * @see org.springframework.security.access.prepost.PostAuthorize */ public interface MethodAuthorizationDeniedHandler { /** * Handle denied method invocations, implementations might either throw an - * {@link org.springframework.security.access.AccessDeniedException} or a replacement - * result instead of invoking the method, e.g. a masked value. + * {@link org.springframework.security.authorization.AuthorizationDeniedException} or + * a replacement result instead of invoking the method, e.g. a masked value. * @param methodInvocation the {@link MethodInvocation} related to the authorization * denied * @param authorizationResult the authorization denied result @@ -41,6 +42,24 @@ public interface MethodAuthorizationDeniedHandler { * {@link reactor.core.publisher.Mono} for reactive applications */ @Nullable - Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult); + Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult); + + /** + * Handle denied method invocations, implementations might either throw an + * {@link org.springframework.security.authorization.AuthorizationDeniedException} or + * a replacement result instead of invoking the method, e.g. a masked value. By + * default, this method invokes + * {@link #handleDeniedInvocation(MethodInvocation, AuthorizationResult)}. + * @param methodInvocationResult the object containing the {@link MethodInvocation} + * and the result produced + * @param authorizationResult the authorization denied result + * @return a replacement result for the denied method invocation, or null, or a + * {@link reactor.core.publisher.Mono} for reactive applications + */ + @Nullable + default Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, + AuthorizationResult authorizationResult) { + return handleDeniedInvocation(methodInvocationResult.getMethodInvocation(), authorizationResult); + } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedPostProcessor.java b/core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedPostProcessor.java deleted file mode 100644 index a72b2b09e3..0000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedPostProcessor.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.authorization.method; - -import org.springframework.lang.Nullable; -import org.springframework.security.authorization.AuthorizationResult; - -/** - * An interface to define a strategy to handle denied method invocation results - * - * @author Marcus da Coregio - * @since 6.3 - * @see org.springframework.security.access.prepost.PostAuthorize - */ -public interface MethodAuthorizationDeniedPostProcessor { - - /** - * Post-process the denied result produced by a method invocation, implementations - * might either throw an - * {@link org.springframework.security.access.AccessDeniedException} or return a - * replacement result instead of the denied result, e.g. a masked value. - * @param methodInvocationResult the object containing the method invocation and the - * result produced - * @param authorizationResult the {@link AuthorizationResult} containing the - * authorization denied details - * @return a replacement result for the denied result, or null, or a - * {@link reactor.core.publisher.Mono} for reactive applications - */ - @Nullable - Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult); - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java index a2cd9d2681..6195373036 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java @@ -38,7 +38,7 @@ import org.springframework.security.core.Authentication; * @since 5.6 */ public final class PostAuthorizeAuthorizationManager - implements AuthorizationManager, MethodAuthorizationDeniedPostProcessor { + implements AuthorizationManager, MethodAuthorizationDeniedHandler { private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); @@ -96,11 +96,19 @@ public final class PostAuthorizeAuthorizationManager } @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation); + PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute; + return postAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult); + } + + @Override + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation()); PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute; - return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult); + return postAuthorizeAttribute.getHandler() + .handleDeniedInvocationResult(methodInvocationResult, authorizationResult); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttribute.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttribute.java index 7c0101d311..3c3348ec24 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttribute.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttribute.java @@ -27,16 +27,16 @@ import org.springframework.util.Assert; */ class PostAuthorizeExpressionAttribute extends ExpressionAttribute { - private final MethodAuthorizationDeniedPostProcessor postProcessor; + private final MethodAuthorizationDeniedHandler handler; - PostAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedPostProcessor postProcessor) { + PostAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedHandler handler) { super(expression); - Assert.notNull(postProcessor, "postProcessor cannot be null"); - this.postProcessor = postProcessor; + Assert.notNull(handler, "handler cannot be null"); + this.handler = handler; } - MethodAuthorizationDeniedPostProcessor getPostProcessor() { - return this.postProcessor; + MethodAuthorizationDeniedHandler getHandler() { + return this.handler; } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java index caa739a11c..42b462ed03 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java @@ -38,12 +38,12 @@ import org.springframework.util.Assert; */ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor(); + private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler(); - private Function, MethodAuthorizationDeniedPostProcessor> postProcessorResolver; + private Function, MethodAuthorizationDeniedHandler> handlerResolver; PostAuthorizeExpressionAttributeRegistry() { - this.postProcessorResolver = (clazz) -> this.defaultPostProcessor; + this.handlerResolver = (clazz) -> this.defaultHandler; } @NonNull @@ -55,22 +55,22 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA return ExpressionAttribute.NULL_ATTRIBUTE; } Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value()); - MethodAuthorizationDeniedPostProcessor postProcessor = resolvePostProcessor(method, targetClass); - return new PostAuthorizeExpressionAttribute(expression, postProcessor); + MethodAuthorizationDeniedHandler deniedHandler = resolveHandler(method, targetClass); + return new PostAuthorizeExpressionAttribute(expression, deniedHandler); } - private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(Method method, Class targetClass) { - Function lookup = AuthorizationAnnotationUtils - .withDefaults(AuthorizationDeniedHandler.class); - AuthorizationDeniedHandler deniedHandler = lookup.apply(method); + private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class targetClass) { + Function lookup = AuthorizationAnnotationUtils + .withDefaults(HandleAuthorizationDenied.class); + HandleAuthorizationDenied deniedHandler = lookup.apply(method); if (deniedHandler != null) { - return this.postProcessorResolver.apply(deniedHandler.postProcessorClass()); + return this.handlerResolver.apply(deniedHandler.handlerClass()); } deniedHandler = lookup.apply(targetClass(method, targetClass)); if (deniedHandler != null) { - return this.postProcessorResolver.apply(deniedHandler.postProcessorClass()); + return this.handlerResolver.apply(deniedHandler.handlerClass()); } - return this.defaultPostProcessor; + return this.defaultHandler; } private PostAuthorize findPostAuthorizeAnnotation(Method method, Class targetClass) { @@ -86,23 +86,23 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA */ void setApplicationContext(ApplicationContext context) { Assert.notNull(context, "context cannot be null"); - this.postProcessorResolver = (postProcessorClass) -> resolvePostProcessor(context, postProcessorClass); + this.handlerResolver = (clazz) -> resolveHandler(context, clazz); } - private MethodAuthorizationDeniedPostProcessor resolvePostProcessor(ApplicationContext context, - Class postProcessorClass) { - if (postProcessorClass == this.defaultPostProcessor.getClass()) { - return this.defaultPostProcessor; + private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context, + Class handlerClass) { + if (handlerClass == this.defaultHandler.getClass()) { + return this.defaultHandler; } - String[] beanNames = context.getBeanNamesForType(postProcessorClass); + String[] beanNames = context.getBeanNamesForType(handlerClass); if (beanNames.length == 0) { - throw new IllegalStateException("Could not find a bean of type " + postProcessorClass.getName()); + throw new IllegalStateException("Could not find a bean of type " + handlerClass.getName()); } if (beanNames.length > 1) { - throw new IllegalStateException("Expected to find a single bean of type " + postProcessorClass.getName() + throw new IllegalStateException("Expected to find a single bean of type " + handlerClass.getName() + " but found " + Arrays.toString(beanNames)); } - return context.getBean(beanNames[0], postProcessorClass); + return context.getBean(beanNames[0], handlerClass); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java index 4c988c0593..475a7a2604 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java @@ -38,7 +38,7 @@ import org.springframework.util.Assert; * @since 5.8 */ public final class PostAuthorizeReactiveAuthorizationManager - implements ReactiveAuthorizationManager, MethodAuthorizationDeniedPostProcessor { + implements ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); @@ -95,11 +95,19 @@ public final class PostAuthorizeReactiveAuthorizationManager } @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation); + PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute; + return postAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult); + } + + @Override + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation()); PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute; - return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult); + return postAuthorizeAttribute.getHandler() + .handleDeniedInvocationResult(methodInvocationResult, authorizationResult); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java index 3895ceb90b..02c26ebf34 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java @@ -86,10 +86,10 @@ public final class PreAuthorizeAuthorizationManager } @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation); - PreAuthorizeExpressionAttribute postAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute; - return postAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult); + PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute; + return preAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java index a224dfcb1b..2bfe20a932 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java @@ -60,9 +60,9 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt } private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class targetClass) { - Function lookup = AuthorizationAnnotationUtils - .withDefaults(AuthorizationDeniedHandler.class); - AuthorizationDeniedHandler deniedHandler = lookup.apply(method); + Function lookup = AuthorizationAnnotationUtils + .withDefaults(HandleAuthorizationDenied.class); + HandleAuthorizationDenied deniedHandler = lookup.apply(method); if (deniedHandler != null) { return this.handlerResolver.apply(deniedHandler.handlerClass()); } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java index b9b9b2cc6e..cc768d861d 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java @@ -90,10 +90,10 @@ public final class PreAuthorizeReactiveAuthorizationManager } @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation); PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute; - return preAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult); + return preAuthorizeAttribute.getHandler().handleDeniedInvocation(methodInvocation, authorizationResult); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedHandler.java b/core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedHandler.java index e92e1345fd..3e4054a738 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedHandler.java +++ b/core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedHandler.java @@ -31,11 +31,20 @@ import org.springframework.security.authorization.AuthorizationResult; public final class ThrowingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { - if (result instanceof AuthorizationDeniedException denied) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + if (authorizationResult instanceof AuthorizationDeniedException denied) { throw denied; } - throw new AuthorizationDeniedException("Access Denied", result); + throw new AuthorizationDeniedException("Access Denied", authorizationResult); + } + + @Override + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, + AuthorizationResult authorizationResult) { + if (authorizationResult instanceof AuthorizationDeniedException denied) { + throw denied; + } + throw new AuthorizationDeniedException("Access Denied", authorizationResult); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedPostProcessor.java b/core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedPostProcessor.java deleted file mode 100644 index 5a7e2c16b1..0000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedPostProcessor.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.authorization.method; - -import org.springframework.security.authorization.AuthorizationDeniedException; -import org.springframework.security.authorization.AuthorizationResult; - -/** - * An implementation of {@link MethodAuthorizationDeniedPostProcessor} that throws - * {@link AuthorizationDeniedException} - * - * @author Marcus da Coregio - * @since 6.3 - */ -public final class ThrowingMethodAuthorizationDeniedPostProcessor implements MethodAuthorizationDeniedPostProcessor { - - @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult result) { - if (result instanceof AuthorizationDeniedException denied) { - throw denied; - } - throw new AuthorizationDeniedException("Access Denied", result); - } - -} diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java index 9073d3eee5..b18fb4fee6 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java @@ -127,7 +127,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class))) + given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class))) .willAnswer(this::masking); given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( @@ -147,7 +147,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class))) + given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class))) .willAnswer((invocation) -> { MethodInvocationResult argument = invocation.getArgument(0); if (!"john".equals(argument.getResult())) { @@ -173,7 +173,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class))) + given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class))) .willAnswer(this::masking); given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( @@ -192,7 +192,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class))) + given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class))) .willAnswer(this::monoMasking); given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( @@ -211,7 +211,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager.class); - given(mockReactiveAuthorizationManager.postProcessResult(any(), any(AuthorizationResult.class))) + given(mockReactiveAuthorizationManager.handleDeniedInvocationResult(any(), any(AuthorizationResult.class))) .willReturn(null); given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty()); AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( @@ -266,7 +266,7 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests { } interface HandlingReactiveAuthorizationManager - extends ReactiveAuthorizationManager, MethodAuthorizationDeniedPostProcessor { + extends ReactiveAuthorizationManager, MethodAuthorizationDeniedHandler { } diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java index 0d5a71d000..400992eec6 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java @@ -127,7 +127,8 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests { HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager.class); given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - given(mockReactiveAuthorizationManager.handle(any(), any(AuthorizationResult.class))).willReturn("***"); + given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class))) + .willReturn("***"); AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); Object result = interceptor.invoke(mockMethodInvocation); @@ -145,7 +146,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests { HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager.class); given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - given(mockReactiveAuthorizationManager.handle(any(), any(AuthorizationResult.class))) + given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class))) .willReturn(Mono.just("***")); AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); @@ -164,7 +165,7 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests { HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( HandlingReactiveAuthorizationManager.class); given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty()); - given(mockReactiveAuthorizationManager.handle(any(), any(AuthorizationResult.class))) + given(mockReactiveAuthorizationManager.handleDeniedInvocation(any(), any(AuthorizationResult.class))) .willReturn(Mono.just("***")); AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( Pointcut.TRUE, mockReactiveAuthorizationManager); diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index 5c2be84260..390b376ea9 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -2256,14 +2256,13 @@ You can also add the Spring Boot property `spring.jackson.default-property-inclu [[fallback-values-authorization-denied]] == Providing Fallback Values When Authorization is Denied -There are some scenarios where you may not wish to throw an `AccessDeniedException` when a method is invoked without the required permissions. -Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases where access denied happened before invoking the method. +There are some scenarios where you may not wish to throw an `AuthorizationDeniedException` when a method is invoked without the required permissions. +Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases where authorization denied happened before invoking the method. -Spring Security provides support for handling and post-processing method access denied by combining {security-api-url}org/springframework/security/authorization/method/AuthorizationDeniedHandler.html[`@AuthorizationDeniedHandler`] with the <> respectively. +Spring Security provides support for handling authorization denied on method invocation by using the {security-api-url}org/springframework/security/authorization/method/HandleAuthorizationDenied.html[`@HandleAuthorizationDenied`]. +The handler works for denied authorizations that happened in the <> as well as {security-api-url}org/springframework/security/authorization/AuthorizationDeniedException.html[`AuthorizationDeniedException`] thrown from the method invocation itself. -=== Using with `@PreAuthorize` - -Let's consider the example from the <>, but instead of creating the `AccessDeniedExceptionInterceptor` to transform an `AccessDeniedException` to a `null` return value, we will use the `handlerClass` attribute from `@AuthorizationDeniedHandler`: +Let's consider the example from the <>, but instead of creating the `AccessDeniedExceptionInterceptor` to transform an `AccessDeniedException` to a `null` return value, we will use the `handlerClass` attribute from `@HandleAuthorizationDenied`: [tabs] ====== @@ -2274,7 +2273,7 @@ Java:: public class NullMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1> @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { return null; } @@ -2295,7 +2294,7 @@ public class User { // ... @PreAuthorize(value = "hasAuthority('user:read')") - @AuthorizationDeniedHandler(handlerClass = NullMethodAuthorizationDeniedHandler.class) + @HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler.class) public String getEmail() { return this.email; } @@ -2308,7 +2307,7 @@ Kotlin:: ---- class NullMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler { <1> - override fun handle(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any { + override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any { return null } @@ -2325,13 +2324,13 @@ class SecurityConfig { } -class User (val name:String, @PreAuthorize(value = "hasAuthority('user:read')") @AuthorizationDeniedHandler(handlerClass = NullMethodAuthorizationDeniedHandler::class) val email:String) <3> +class User (val name:String, @PreAuthorize(value = "hasAuthority('user:read')") @HandleAuthorizationDenied(handlerClass = NullMethodAuthorizationDeniedHandler::class) val email:String) <3> ---- ====== <1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a `null` value <2> Register the `NullMethodAuthorizationDeniedHandler` as a bean -<3> Annotate the method with `@AuthorizationDeniedHandler` and pass the `NullMethodAuthorizationDeniedHandler` to the `handlerClass` attribute +<3> Annotate the method with `@HandleAuthorizationDenied` and pass the `NullMethodAuthorizationDeniedHandler` to the `handlerClass` attribute And then you can verify that a `null` value is returned instead of the `AccessDeniedException`: @@ -2371,9 +2370,12 @@ fun getEmailWhenProxiedThenNullEmail() { ---- ====== -=== Using with `@PostAuthorize` +=== Using the Denied Result From the Method Invocation -The same can be achieved with `@PostAuthorize`, however, since `@PostAuthorize` checks are performed after the method is invoked, we have access to the resulting value of the invocation, allowing you to provide fallback values based on the unauthorized results. +There are some scenarios where you might want to return a secure result derived from the denied result. +For example, if a user is not authorized to see email addresses, you might want to apply some masking on the original email address, i.e. _useremail@example.com_ would become _use\\******@example.com_. + +For those scenarios, you can override the `handleDeniedInvocationResult` from the `MethodAuthorizationDeniedHandler`, which has the {security-api-url}org/springframework/security/authorization/method/MethodInvocationResult.html[`MethodInvocationResult`] as an argument. Let's continue with the previous example, but instead of returning `null`, we will return a masked value of the email: [tabs] @@ -2382,10 +2384,15 @@ Java:: + [source,java,role="primary"] ---- -public class EmailMaskingMethodAuthorizationDeniedPostProcessor implements MethodAuthorizationDeniedPostProcessor { <1> +public class EmailMaskingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { <1> @Override - public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + return "***"; + } + + @Override + public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) { String email = (String) methodInvocationResult.getResult(); return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*"); } @@ -2397,8 +2404,8 @@ public class EmailMaskingMethodAuthorizationDeniedPostProcessor implements Metho public class SecurityConfig { @Bean <2> - public EmailMaskingMethodAuthorizationDeniedPostProcessor emailMaskingMethodAuthorizationDeniedPostProcessor() { - return new EmailMaskingMethodAuthorizationDeniedPostProcessor(); + public EmailMaskingMethodAuthorizationDeniedHandler emailMaskingMethodAuthorizationDeniedHandler() { + return new EmailMaskingMethodAuthorizationDeniedHandler(); } } @@ -2407,7 +2414,7 @@ public class User { // ... @PostAuthorize(value = "hasAuthority('user:read')") - @AuthorizationDeniedHandler(postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor.class) + @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler.class) public String getEmail() { return this.email; } @@ -2418,9 +2425,13 @@ Kotlin:: + [source,kotlin,role="secondary"] ---- -class EmailMaskingMethodAuthorizationDeniedPostProcessor : MethodAuthorizationDeniedPostProcessor { +class EmailMaskingMethodAuthorizationDeniedHandler : MethodAuthorizationDeniedHandler { - override fun postProcessResult(methodInvocationResult: MethodInvocationResult, authorizationResult: AuthorizationResult): Any { + override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any { + return "***" + } + + override fun handleDeniedInvocationResult(methodInvocationResult: MethodInvocationResult, authorizationResult: AuthorizationResult): Any { val email = methodInvocationResult.result as String return email.replace("(^[^@]{3}|(?!^)\\G)[^@]".toRegex(), "$1*") } @@ -2432,22 +2443,27 @@ class EmailMaskingMethodAuthorizationDeniedPostProcessor : MethodAuthorizationDe class SecurityConfig { @Bean - fun emailMaskingMethodAuthorizationDeniedPostProcessor(): EmailMaskingMethodAuthorizationDeniedPostProcessor { - return EmailMaskingMethodAuthorizationDeniedPostProcessor() + fun emailMaskingMethodAuthorizationDeniedHandler(): EmailMaskingMethodAuthorizationDeniedHandler { + return EmailMaskingMethodAuthorizationDeniedHandler() } } -class User (val name:String, @PostAuthorize(value = "hasAuthority('user:read')") @AuthorizationDeniedHandler(postProcessorClass = EmailMaskingMethodAuthorizationDeniedPostProcessor::class) val email:String) <3> +class User (val name:String, @PostAuthorize(value = "hasAuthority('user:read')") @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler::class) val email:String) <3> ---- ====== -<1> Create an implementation of `MethodAuthorizationDeniedPostProcessor` that returns a masked value of the unauthorized result value -<2> Register the `EmailMaskingMethodAuthorizationDeniedPostProcessor` as a bean -<3> Annotate the method with `@AuthorizationDeniedHandler` and pass the `EmailMaskingMethodAuthorizationDeniedPostProcessor` to the `postProcessorClass` attribute +<1> Create an implementation of `MethodAuthorizationDeniedHandler` that returns a masked value of the unauthorized result value +<2> Register the `EmailMaskingMethodAuthorizationDeniedHandler` as a bean +<3> Annotate the method with `@HandleAuthorizationDenied` and pass the `EmailMaskingMethodAuthorizationDeniedHandler` to the `handlerClass` attribute And then you can verify that a masked email is returned instead of an `AccessDeniedException`: +[WARNING] +==== +Since you have access to the original denied value, make sure that you correctly handle it and do not return it to the caller. +==== + [tabs] ====== Java:: @@ -2481,20 +2497,20 @@ fun getEmailWhenProxiedThenMaskedEmail() { ---- ====== -When implementing the `MethodAuthorizationDeniedHandler` or the `MethodAuthorizationDeniedPostProcessor` you have a few options on what you can return: +When implementing the `MethodAuthorizationDeniedHandler` you have a few options on what type you can return: - A `null` value. - A non-null value, respecting the method's return type. -- Throw an exception, usually an instance of `AccessDeniedException`. This is the default behavior. +- Throw an exception, usually an instance of `AuthorizationDeniedException`. This is the default behavior. - A `Mono` type for reactive applications. -Note that since the handler and the post-processor must be registered as beans, you can inject dependencies into them if you need a more complex logic. +Note that since the handler must be registered as beans in your application context, you can inject dependencies into them if you need a more complex logic. In addition to that, you have available the `MethodInvocation` or the `MethodInvocationResult`, as well as the `AuthorizationResult` for more details related to the authorization decision. [[deciding-return-based-parameters]] === Deciding What to Return Based on Available Parameters -Consider a scenario where there might be multiple mask values for different methods, it would be not so productive if we had to create a handler or post-processor for each of those methods, although it is perfectly fine to do that. +Consider a scenario where there might be multiple mask values for different methods, it would be not so productive if we had to create a handler for each of those methods, although it is perfectly fine to do that. In such cases, we can use the information passed via parameters to decide what to do. For example, we can create a custom `@Mask` annotation and a handler that detects that annotation to decide what mask value to return: @@ -2517,7 +2533,7 @@ public @interface Mask { public class MaskAnnotationDeniedHandler implements MethodAuthorizationDeniedHandler { @Override - public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { + public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class); return mask.value(); } @@ -2539,14 +2555,14 @@ public class SecurityConfig { public class MyService { @PreAuthorize(value = "hasAuthority('user:read')") - @AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class) @Mask("***") public String foo() { return "foo"; } @PreAuthorize(value = "hasAuthority('user:read')") - @AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class) @Mask("???") public String bar() { return "bar"; @@ -2567,7 +2583,7 @@ annotation class Mask(val value: String) class MaskAnnotationDeniedHandler : MethodAuthorizationDeniedHandler { - override fun handle(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any { + override fun handleDeniedInvocation(methodInvocation: MethodInvocation, authorizationResult: AuthorizationResult): Any { val mask = AnnotationUtils.getAnnotation(methodInvocation.method, Mask::class.java) return mask.value } @@ -2589,14 +2605,14 @@ class SecurityConfig { class MyService { @PreAuthorize(value = "hasAuthority('user:read')") - @AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class) @Mask("***") fun foo(): String { return "foo" } @PreAuthorize(value = "hasAuthority('user:read')") - @AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class) + @HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class) @Mask("???") fun bar(): String { return "bar" @@ -2653,8 +2669,8 @@ fun barWhenDeniedThenReturnQuestionMarks() { === Combining with Meta Annotation Support -You can also combine the `@AuthorizationDeniedHandler` with other annotations in order to reduce and simplify the annotations in a method. -Let's consider the <> and merge `@AuthorizationDeniedHandler` with `@Mask`: +You can also combine the `@HandleAuthorizationDenied` with other annotations in order to reduce and simplify the annotations in a method. +Let's consider the <> and merge `@HandleAuthorizationDenied` with `@Mask`: [tabs] ====== @@ -2664,7 +2680,7 @@ Java:: ---- @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) -@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler.class) +@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler.class) public @interface Mask { String value(); @@ -2683,7 +2699,7 @@ Kotlin:: ---- @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) -@AuthorizationDeniedHandler(handlerClass = MaskAnnotationDeniedHandler::class) +@HandleAuthorizationDenied(handlerClass = MaskAnnotationDeniedHandler::class) annotation class Mask(val value: String) @Mask("***")