Browse Source

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
Marcus Hert Da Coregio 1 năm trước cách đây
mục cha
commit
2fbbcc4bd0
31 tập tin đã thay đổi với 425 bổ sung440 xóa
  1. 5 12
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java
  2. 6 13
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java
  3. 50 30
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java
  4. 4 3
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java
  5. 19 30
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java
  6. 11 16
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java
  7. 7 16
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java
  8. 42 28
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java
  9. 4 3
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java
  10. 13 5
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/UserRecordWithEmailProtected.java
  11. 6 13
      core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java
  12. 6 13
      core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java
  13. 17 15
      core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java
  14. 36 19
      core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java
  15. 22 3
      core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java
  16. 12 26
      core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java
  17. 8 14
      core/src/main/java/org/springframework/security/authorization/method/HandleAuthorizationDenied.java
  18. 22 3
      core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedHandler.java
  19. 0 46
      core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedPostProcessor.java
  20. 11 3
      core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java
  21. 6 6
      core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttribute.java
  22. 21 21
      core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java
  23. 11 3
      core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java
  24. 3 3
      core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java
  25. 3 3
      core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java
  26. 2 2
      core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java
  27. 12 3
      core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedHandler.java
  28. 0 39
      core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedPostProcessor.java
  29. 6 6
      core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java
  30. 4 3
      core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java
  31. 56 40
      docs/modules/ROOT/pages/servlet/authorization/method-security.adoc

+ 5 - 12
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<T>
-		implements AuthorizationManager<T>, MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
+		implements AuthorizationManager<T>, MethodAuthorizationDeniedHandler {
 
 	private final Supplier<AuthorizationManager<T>> delegate;
 
 	private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
 
-	private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
-
 	DeferringObservationAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
 			AuthorizationManager<T> delegate) {
 		this.delegate = SingletonSupplier.of(() -> {
@@ -55,9 +51,6 @@ final class DeferringObservationAuthorizationManager<T>
 		if (delegate instanceof MethodAuthorizationDeniedHandler h) {
 			this.handler = h;
 		}
-		if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
-			this.postProcessor = p;
-		}
 	}
 
 	@Override
@@ -66,14 +59,14 @@ final class DeferringObservationAuthorizationManager<T>
 	}
 
 	@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);
 	}
 
 }

+ 6 - 13
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<T> implements ReactiveAuthorizationManager<T>,
-		MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
+final class DeferringObservationReactiveAuthorizationManager<T>
+		implements ReactiveAuthorizationManager<T>, MethodAuthorizationDeniedHandler {
 
 	private final Supplier<ReactiveAuthorizationManager<T>> delegate;
 
 	private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
 
-	private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
-
 	DeferringObservationReactiveAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
 			ReactiveAuthorizationManager<T> delegate) {
 		this.delegate = SingletonSupplier.of(() -> {
@@ -56,9 +52,6 @@ final class DeferringObservationReactiveAuthorizationManager<T> 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<T> 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);
 	}
 
 }

+ 50 - 30
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;
 		}
 

+ 4 - 3
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

+ 19 - 30
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<ConfigurableWebApplicationContext> 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;
-		}
-
 	}
 
 }

+ 11 - 16
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

+ 7 - 16
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<User.UserBuilder> 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;
-		}
-
 	}
 
 }

+ 42 - 28
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<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber);
 
 	@PreAuthorize("hasRole('ADMIN')")
-	@AuthorizationDeniedHandler(handlerClass = StartMaskingHandlerChild.class)
+	@HandleAuthorizationDenied(handlerClass = StartMaskingHandlerChild.class)
 	Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
 
 	@PreAuthorize("hasRole('ADMIN')")
-	@AuthorizationDeniedHandler(handlerClass = StarMaskingHandler.class)
+	@HandleAuthorizationDenied(handlerClass = StarMaskingHandler.class)
 	Mono<String> preAuthorizeThrowAccessDeniedManually();
 
 	@PostAuthorize("hasRole('ADMIN')")
-	@AuthorizationDeniedHandler(postProcessorClass = CardNumberMaskingPostProcessor.class)
+	@HandleAuthorizationDenied(handlerClass = CardNumberMaskingPostProcessor.class)
 	Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber);
 
 	@PostAuthorize("hasRole('ADMIN')")
-	@AuthorizationDeniedHandler(postProcessorClass = PostMaskingPostProcessor.class)
+	@HandleAuthorizationDenied(handlerClass = PostMaskingPostProcessor.class)
 	Mono<String> postAuthorizeThrowAccessDeniedManually();
 
 	@PreAuthorize("denyAll()")
 	@Mask("methodmask")
-	@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
+	@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
 	Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation();
 
 	@PreAuthorize("denyAll()")
-	@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
+	@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
 	Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation();
 
 	@NullDenied(role = "ADMIN")
@@ -80,33 +79,32 @@ public interface ReactiveMethodSecurityService {
 
 	@PostAuthorize("denyAll()")
 	@Mask("methodmask")
-	@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
+	@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
 	Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation();
 
 	@PostAuthorize("denyAll()")
-	@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
+	@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
 	Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation();
 
 	@PreAuthorize("hasRole('ADMIN')")
 	@Mask(expression = "@myMasker.getMask()")
-	@AuthorizationDeniedHandler(handlerClass = MaskAnnotationHandler.class)
+	@HandleAuthorizationDenied(handlerClass = MaskAnnotationHandler.class)
 	Mono<String> preAuthorizeWithMaskAnnotationUsingBean();
 
 	@PostAuthorize("hasRole('ADMIN')")
 	@Mask(expression = "@myMasker.getMask(returnObject)")
-	@AuthorizationDeniedHandler(postProcessorClass = MaskAnnotationPostProcessor.class)
+	@HandleAuthorizationDenied(handlerClass = MaskAnnotationPostProcessor.class)
 	Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
 
 	@PreAuthorize("@authz.checkReactiveResult(#result)")
 	@PostAuthorize("@authz.checkReactiveResult(!#result)")
-	@AuthorizationDeniedHandler(handlerClass = MethodAuthorizationDeniedHandler.class,
-			postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
+	@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
 	Mono<String> 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();

+ 4 - 3
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<String> 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<String> postAuthorizeThrowAccessDeniedManually() {
-		return Mono.error(new AccessDeniedException("Access Denied"));
+		return Mono.error(new AuthorizationDeniedException("Access Denied", new AuthorizationDecision(false)));
 	}
 
 	@Override

+ 13 - 5
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 handleDeniedInvocation(MethodInvocation methodInvocation,
+				AuthorizationResult authorizationResult) {
+			return "***";
+		}
 
 		@Override
-		public Object postProcessResult(MethodInvocationResult methodInvocationResult,
+		public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult,
 				AuthorizationResult authorizationResult) {
 			String email = (String) methodInvocationResult.getResult();
 			return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*");

+ 6 - 13
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<T> implements AuthorizationManager<T>, MessageSourceAware,
-		MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
+public final class ObservationAuthorizationManager<T>
+		implements AuthorizationManager<T>, MessageSourceAware, MethodAuthorizationDeniedHandler {
 
 	private final ObservationRegistry registry;
 
@@ -55,17 +53,12 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
 
 	private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
 
-	private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
-
 	public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager<T> 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<T> 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);
 	}
 
 }

+ 6 - 13
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<T> implements ReactiveAuthorizationManager<T>,
-		MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {
+public final class ObservationReactiveAuthorizationManager<T>
+		implements ReactiveAuthorizationManager<T>, MethodAuthorizationDeniedHandler {
 
 	private final ObservationRegistry registry;
 
@@ -49,8 +47,6 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
 
 	private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
 
-	private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
-
 	public ObservationReactiveAuthorizationManager(ObservationRegistry registry,
 			ReactiveAuthorizationManager<T> delegate) {
 		this.registry = registry;
@@ -58,9 +54,6 @@ public final class ObservationReactiveAuthorizationManager<T> 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<T> 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);
 	}
 
 }

+ 17 - 15
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<MethodInvocationResult> 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() {

+ 36 - 19
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> authentication = ReactiveAuthenticationUtils.getAuthentication();
-		Function<Object, Mono<?>> postAuthorize = (result) -> postAuthorize(authentication, mi, result);
+		Function<Signal<?>, 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<Object> 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.defaultHandler.handleDeniedInvocationResult(methodInvocationResult, decision);
+		}).flatMap((processedResult) -> {
+			if (Mono.class.isAssignableFrom(processedResult.getClass())) {
+				return (Mono<?>) processedResult;
+			}
+			return Mono.justOrEmpty(processedResult);
+		});
+	}
+
+	private Mono<Object> postProcess(AuthorizationResult decision, MethodInvocation methodInvocation) {
+		return Mono.fromSupplier(() -> {
+			if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
+				return handler.handleDeniedInvocation(methodInvocation, decision);
 			}
-			return this.defaultPostProcessor.postProcessResult(methodInvocationResult, decision);
+			return this.defaultHandler.handleDeniedInvocation(methodInvocation, decision);
 		}).flatMap((processedResult) -> {
 			if (Mono.class.isAssignableFrom(processedResult.getClass())) {
 				return (Mono<?>) processedResult;

+ 22 - 3
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() {

+ 12 - 26
core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java

@@ -142,19 +142,12 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
 		Mono<Authentication> 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> 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);
-				}
-				if (signal.getThrowable() instanceof AuthorizationDeniedException denied) {
-					return postProcess(denied, mi);
+			.flatMap((decision) -> {
+				if (decision.isGranted()) {
+					return mapping.onErrorResume(AuthorizationDeniedException.class,
+							(deniedEx) -> postProcess(deniedEx, mi));
 				}
-				return Mono.error(signal.getThrowable());
+				return postProcess(decision, mi);
 			});
 	}
 
 	private Mono<Object> 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;

+ 8 - 14
core/src/main/java/org/springframework/security/authorization/method/AuthorizationDeniedHandler.java → 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<? extends MethodAuthorizationDeniedHandler> handlerClass() default ThrowingMethodAuthorizationDeniedHandler.class;
 
-	/**
-	 * The {@link MethodAuthorizationDeniedPostProcessor} used to post process denied
-	 * authorizations from
-	 * {@link org.springframework.security.access.prepost.PostAuthorize}
-	 * @return
-	 */
-	Class<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass() default ThrowingMethodAuthorizationDeniedPostProcessor.class;
-
 }

+ 22 - 3
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);
+	}
 
 }

+ 0 - 46
core/src/main/java/org/springframework/security/authorization/method/MethodAuthorizationDeniedPostProcessor.java

@@ -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);
-
-}

+ 11 - 3
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<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
+		implements AuthorizationManager<MethodInvocationResult>, 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);
 	}
 
 }

+ 6 - 6
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;
 	}
 
 }

+ 21 - 21
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<ExpressionAttribute> {
 
-	private final MethodAuthorizationDeniedPostProcessor defaultPostProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
+	private final MethodAuthorizationDeniedHandler defaultHandler = new ThrowingMethodAuthorizationDeniedHandler();
 
-	private Function<Class<? extends MethodAuthorizationDeniedPostProcessor>, MethodAuthorizationDeniedPostProcessor> postProcessorResolver;
+	private Function<Class<? extends MethodAuthorizationDeniedHandler>, 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<AnnotatedElement, AuthorizationDeniedHandler> lookup = AuthorizationAnnotationUtils
-			.withDefaults(AuthorizationDeniedHandler.class);
-		AuthorizationDeniedHandler deniedHandler = lookup.apply(method);
+	private MethodAuthorizationDeniedHandler resolveHandler(Method method, Class<?> targetClass) {
+		Function<AnnotatedElement, HandleAuthorizationDenied> 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<? extends MethodAuthorizationDeniedPostProcessor> postProcessorClass) {
-		if (postProcessorClass == this.defaultPostProcessor.getClass()) {
-			return this.defaultPostProcessor;
+	private MethodAuthorizationDeniedHandler resolveHandler(ApplicationContext context,
+			Class<? extends MethodAuthorizationDeniedHandler> 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);
 	}
 
 }

+ 11 - 3
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<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
+		implements ReactiveAuthorizationManager<MethodInvocationResult>, 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);
 	}
 
 }

+ 3 - 3
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);
 	}
 
 }

+ 3 - 3
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<AnnotatedElement, AuthorizationDeniedHandler> lookup = AuthorizationAnnotationUtils
-			.withDefaults(AuthorizationDeniedHandler.class);
-		AuthorizationDeniedHandler deniedHandler = lookup.apply(method);
+		Function<AnnotatedElement, HandleAuthorizationDenied> lookup = AuthorizationAnnotationUtils
+			.withDefaults(HandleAuthorizationDenied.class);
+		HandleAuthorizationDenied deniedHandler = lookup.apply(method);
 		if (deniedHandler != null) {
 			return this.handlerResolver.apply(deniedHandler.handlerClass());
 		}

+ 2 - 2
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);
 	}
 
 }

+ 12 - 3
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);
 	}
 
 }

+ 0 - 39
core/src/main/java/org/springframework/security/authorization/method/ThrowingMethodAuthorizationDeniedPostProcessor.java

@@ -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);
-	}
-
-}

+ 6 - 6
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<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
+			extends ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedHandler {
 
 	}
 

+ 4 - 3
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);

+ 56 - 40
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 <<authorizing-with-annotations,`@PreAuthorize` and `@PostAuthorize` annotations>> 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 <<authorizing-with-annotations,`@PreAuthorize` and `@PostAuthorize` annotations>> 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 <<authorize-object,previous section>>, 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 <<authorize-object,previous section>>, 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
+
+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_.
 
-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.
+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 handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
+        return "***";
+    }
 
     @Override
-    public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) {
+    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 <<deciding-return-based-parameters,example from the previous section>> 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 <<deciding-return-based-parameters,example from the previous section>> 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("***")