Browse Source

Revert "Support SpEL Returning AuthorizationDecision"

This reverts commit 77f2977c55842a717f8cb5c0344a7dd14b39c794.
Josh Cummings 1 year ago
parent
commit
0a9c482f62
28 changed files with 199 additions and 520 deletions
  1. 1 30
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java
  2. 1 30
      config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java
  3. 0 18
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java
  4. 0 5
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java
  5. 0 10
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java
  6. 0 5
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java
  7. 0 40
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java
  8. 1 45
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java
  9. 0 5
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java
  10. 0 5
      config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java
  11. 1 32
      core/src/main/java/org/springframework/security/access/expression/ExpressionUtils.java
  12. 1 29
      core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java
  13. 1 29
      core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java
  14. 1 1
      core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java
  15. 1 1
      core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java
  16. 1 1
      core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java
  17. 1 1
      core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java
  18. 41 0
      core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationDecision.java
  19. 5 12
      core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java
  20. 4 13
      core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java
  21. 43 0
      core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationDecision.java
  22. 5 11
      core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java
  23. 4 12
      core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java
  24. 3 32
      core/src/main/java/org/springframework/security/authorization/method/ReactiveExpressionUtils.java
  25. 0 70
      core/src/test/java/org/springframework/security/access/expression/ExpressionUtilsTests.java
  26. 52 33
      core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java
  27. 32 14
      core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java
  28. 0 36
      docs/modules/ROOT/pages/servlet/authorization/method-security.adoc

+ 1 - 30
config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationAuthorizationManager.java

@@ -19,30 +19,18 @@ package org.springframework.security.config.annotation.method.configuration;
 import java.util.function.Supplier;
 
 import io.micrometer.observation.ObservationRegistry;
-import org.aopalliance.intercept.MethodInvocation;
 
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.security.authorization.AuthorizationDecision;
 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 {
+final class DeferringObservationAuthorizationManager<T> implements AuthorizationManager<T> {
 
 	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(() -> {
@@ -52,12 +40,6 @@ final class DeferringObservationAuthorizationManager<T>
 			}
 			return new ObservationAuthorizationManager<>(registry, delegate);
 		});
-		if (delegate instanceof MethodAuthorizationDeniedHandler h) {
-			this.handler = h;
-		}
-		if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
-			this.postProcessor = p;
-		}
 	}
 
 	@Override
@@ -65,15 +47,4 @@ final class DeferringObservationAuthorizationManager<T>
 		return this.delegate.get().check(authentication, object);
 	}
 
-	@Override
-	public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
-		return this.handler.handle(methodInvocation, authorizationResult);
-	}
-
-	@Override
-	public Object postProcessResult(MethodInvocationResult methodInvocationResult,
-			AuthorizationResult authorizationResult) {
-		return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
-	}
-
 }

+ 1 - 30
config/src/main/java/org/springframework/security/config/annotation/method/configuration/DeferringObservationReactiveAuthorizationManager.java

@@ -19,31 +19,19 @@ package org.springframework.security.config.annotation.method.configuration;
 import java.util.function.Supplier;
 
 import io.micrometer.observation.ObservationRegistry;
-import org.aopalliance.intercept.MethodInvocation;
 import reactor.core.publisher.Mono;
 
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.security.authorization.AuthorizationDecision;
-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> {
 
 	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(() -> {
@@ -53,12 +41,6 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
 			}
 			return new ObservationReactiveAuthorizationManager<>(registry, delegate);
 		});
-		if (delegate instanceof MethodAuthorizationDeniedHandler h) {
-			this.handler = h;
-		}
-		if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
-			this.postProcessor = p;
-		}
 	}
 
 	@Override
@@ -66,15 +48,4 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
 		return this.delegate.get().check(authentication, object);
 	}
 
-	@Override
-	public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
-		return this.handler.handle(methodInvocation, authorizationResult);
-	}
-
-	@Override
-	public Object postProcessResult(MethodInvocationResult methodInvocationResult,
-			AuthorizationResult authorizationResult) {
-		return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
-	}
-
 }

+ 0 - 18
config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java

@@ -18,8 +18,6 @@ package org.springframework.security.config.annotation.method.configuration;
 
 import reactor.core.publisher.Mono;
 
-import org.springframework.security.authorization.AuthorizationDecision;
-import org.springframework.security.authorization.AuthorizationResult;
 import org.springframework.security.core.Authentication;
 import org.springframework.stereotype.Component;
 
@@ -47,20 +45,4 @@ public class Authz {
 		return message != null && message.contains(authentication.getName());
 	}
 
-	public AuthorizationResult checkResult(boolean result) {
-		return new AuthzResult(result);
-	}
-
-	public Mono<AuthorizationResult> checkReactiveResult(boolean result) {
-		return Mono.just(checkResult(result));
-	}
-
-	public static class AuthzResult extends AuthorizationDecision {
-
-		public AuthzResult(boolean granted) {
-			super(granted);
-		}
-
-	}
-
 }

+ 0 - 5
config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityService.java

@@ -173,11 +173,6 @@ public interface MethodSecurityService {
 	@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = UserFallbackDeniedHandler.class)
 	UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
 
-	@PreAuthorize(value = "@authz.checkResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class)
-	@PostAuthorize(value = "@authz.checkResult(!#result)",
-			postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
-	String checkCustomResult(boolean result);
-
 	class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
 
 		@Override

+ 0 - 10
config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceConfig.java

@@ -28,14 +28,4 @@ public class MethodSecurityServiceConfig {
 		return new MethodSecurityServiceImpl();
 	}
 
-	@Bean
-	ReactiveMethodSecurityService reactiveService() {
-		return new ReactiveMethodSecurityServiceImpl();
-	}
-
-	@Bean
-	Authz authz() {
-		return new Authz();
-	}
-
 }

+ 0 - 5
config/src/test/java/org/springframework/security/config/annotation/method/configuration/MethodSecurityServiceImpl.java

@@ -197,9 +197,4 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
 		return new UserRecordWithEmailProtected("username", "useremail@example.com");
 	}
 
-	@Override
-	public String checkCustomResult(boolean result) {
-		return "ok";
-	}
-
 }

+ 0 - 40
config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

@@ -66,8 +66,6 @@ import org.springframework.security.authorization.method.AuthorizationAdvisorPro
 import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
 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;
@@ -94,8 +92,6 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 /**
  * Tests for {@link PrePostMethodSecurityConfiguration}.
@@ -929,23 +925,6 @@ public class PrePostMethodSecurityConfigurationTests {
 		assertThat(user.name()).isEqualTo("Protected");
 	}
 
-	@Test
-	@WithMockUser
-	void getUserWhenNotAuthorizedThenHandlerUsesCustomAuthorizationDecision() {
-		this.spring.register(MethodSecurityServiceConfig.class, CustomResultConfig.class).autowire();
-		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);
-		assertThat(service.checkCustomResult(true)).isNull();
-		verify(postProcessor).postProcessResult(any(), any(Authz.AuthzResult.class));
-		verifyNoMoreInteractions(handler);
-	}
-
 	private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
 		return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
 	}
@@ -1470,23 +1449,4 @@ public class PrePostMethodSecurityConfigurationTests {
 
 	}
 
-	@EnableMethodSecurity
-	static class CustomResultConfig {
-
-		MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class);
-
-		MethodAuthorizationDeniedPostProcessor postProcessor = mock(MethodAuthorizationDeniedPostProcessor.class);
-
-		@Bean
-		MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
-			return this.handler;
-		}
-
-		@Bean
-		MethodAuthorizationDeniedPostProcessor methodAuthorizationDeniedPostProcessor() {
-			return this.postProcessor;
-		}
-
-	}
-
 }

+ 1 - 45
config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfigurationTests.java

@@ -47,8 +47,6 @@ import org.springframework.security.authentication.TestAuthentication;
 import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
 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;
@@ -56,14 +54,8 @@ import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.ReactiveSecurityContextHolder;
 import org.springframework.security.core.userdetails.User;
-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.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 /**
  * @author Tadaya Tsuyukubo
@@ -73,7 +65,7 @@ public class ReactiveMethodSecurityConfigurationTests {
 
 	public final SpringTestContext spring = new SpringTestContext(this);
 
-	@Autowired(required = false)
+	@Autowired
 	DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler;
 
 	@Test
@@ -220,23 +212,6 @@ public class ReactiveMethodSecurityConfigurationTests {
 			.verifyError(AccessDeniedException.class);
 	}
 
-	@Test
-	@WithMockUser
-	void getUserWhenNotAuthorizedThenHandlerUsesCustomAuthorizationDecision() {
-		this.spring.register(MethodSecurityServiceConfig.class, CustomResultConfig.class).autowire();
-		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);
-		assertThat(service.checkCustomResult(true).block()).isNull();
-		verify(postProcessor).postProcessResult(any(), any(Authz.AuthzResult.class));
-		verifyNoMoreInteractions(handler);
-	}
-
 	private static Consumer<User.UserBuilder> authorities(String... authorities) {
 		return (builder) -> builder.authorities(authorities);
 	}
@@ -378,23 +353,4 @@ public class ReactiveMethodSecurityConfigurationTests {
 
 	}
 
-	@EnableReactiveMethodSecurity
-	static class CustomResultConfig {
-
-		MethodAuthorizationDeniedHandler handler = mock(MethodAuthorizationDeniedHandler.class);
-
-		MethodAuthorizationDeniedPostProcessor postProcessor = mock(MethodAuthorizationDeniedPostProcessor.class);
-
-		@Bean
-		MethodAuthorizationDeniedHandler methodAuthorizationDeniedHandler() {
-			return this.handler;
-		}
-
-		@Bean
-		MethodAuthorizationDeniedPostProcessor methodAuthorizationDeniedPostProcessor() {
-			return this.postProcessor;
-		}
-
-	}
-
 }

+ 0 - 5
config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java

@@ -85,11 +85,6 @@ public interface ReactiveMethodSecurityService {
 	@Mask(expression = "@myMasker.getMask(returnObject)")
 	Mono<String> postAuthorizeWithMaskAnnotationUsingBean();
 
-	@PreAuthorize(value = "@authz.checkReactiveResult(#result)", handlerClass = MethodAuthorizationDeniedHandler.class)
-	@PostAuthorize(value = "@authz.checkReactiveResult(!#result)",
-			postProcessorClass = MethodAuthorizationDeniedPostProcessor.class)
-	Mono<String> checkCustomResult(boolean result);
-
 	class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
 
 		@Override

+ 0 - 5
config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java

@@ -82,9 +82,4 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
 		return Mono.just("ok");
 	}
 
-	@Override
-	public Mono<String> checkCustomResult(boolean result) {
-		return Mono.just("ok");
-	}
-
 }

+ 1 - 32
core/src/main/java/org/springframework/security/access/expression/ExpressionUtils.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -19,43 +19,12 @@ package org.springframework.security.access.expression;
 import org.springframework.expression.EvaluationContext;
 import org.springframework.expression.EvaluationException;
 import org.springframework.expression.Expression;
-import org.springframework.security.authorization.AuthorizationDecision;
-import org.springframework.security.authorization.AuthorizationResult;
-import org.springframework.security.authorization.ExpressionAuthorizationDecision;
 
 public final class ExpressionUtils {
 
 	private ExpressionUtils() {
 	}
 
-	/**
-	 * Evaluate a SpEL expression and coerce into an {@link AuthorizationDecision}
-	 * @param expr a SpEL expression
-	 * @param ctx an {@link EvaluationContext}
-	 * @return the resulting {@link AuthorizationDecision}
-	 * @since 6.3
-	 */
-	public static AuthorizationResult evaluate(Expression expr, EvaluationContext ctx) {
-		try {
-			Object result = expr.getValue(ctx);
-			if (result instanceof AuthorizationResult decision) {
-				return decision;
-			}
-			if (result instanceof Boolean granted) {
-				return new ExpressionAuthorizationDecision(granted, expr);
-			}
-			if (result == null) {
-				return null;
-			}
-			throw new IllegalArgumentException(
-					"SpEL expression must return either a Boolean or an AuthorizationDecision");
-		}
-		catch (EvaluationException ex) {
-			throw new IllegalArgumentException("Failed to evaluate expression '" + expr.getExpressionString() + "'",
-					ex);
-		}
-	}
-
 	public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
 		try {
 			return expr.getValue(ctx, Boolean.class);

+ 1 - 29
core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java

@@ -21,17 +21,11 @@ import java.util.function.Supplier;
 import io.micrometer.observation.Observation;
 import io.micrometer.observation.ObservationConvention;
 import io.micrometer.observation.ObservationRegistry;
-import org.aopalliance.intercept.MethodInvocation;
 
 import org.springframework.context.MessageSource;
 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 +36,7 @@ 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 {
 
 	private final ObservationRegistry registry;
 
@@ -53,19 +46,9 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
 
 	private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
 
-	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
@@ -115,15 +98,4 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa
 		this.messages = new MessageSourceAccessor(messageSource);
 	}
 
-	@Override
-	public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
-		return this.handler.handle(methodInvocation, authorizationResult);
-	}
-
-	@Override
-	public Object postProcessResult(MethodInvocationResult methodInvocationResult,
-			AuthorizationResult authorizationResult) {
-		return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
-	}
-
 }

+ 1 - 29
core/src/main/java/org/springframework/security/authorization/ObservationReactiveAuthorizationManager.java

@@ -20,15 +20,9 @@ import io.micrometer.observation.Observation;
 import io.micrometer.observation.ObservationConvention;
 import io.micrometer.observation.ObservationRegistry;
 import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
-import org.aopalliance.intercept.MethodInvocation;
 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 +32,7 @@ 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> {
 
 	private final ObservationRegistry registry;
 
@@ -47,20 +40,10 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
 
 	private ObservationConvention<AuthorizationObservationContext<?>> convention = new AuthorizationObservationConvention();
 
-	private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();
-
-	private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();
-
 	public ObservationReactiveAuthorizationManager(ObservationRegistry registry,
 			ReactiveAuthorizationManager<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
@@ -98,15 +81,4 @@ public final class ObservationReactiveAuthorizationManager<T> implements Reactiv
 		this.convention = convention;
 	}
 
-	@Override
-	public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
-		return this.handler.handle(methodInvocation, authorizationResult);
-	}
-
-	@Override
-	public Object postProcessResult(MethodInvocationResult methodInvocationResult,
-			AuthorizationResult authorizationResult) {
-		return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
-	}
-
 }

+ 1 - 1
core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.java

@@ -184,7 +184,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
 	}
 
 	private Object postProcess(MethodInvocationResult mi, AuthorizationDecision decision) {
-		if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
+		if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
 			return postProcessableDecision.postProcessResult(mi, decision);
 		}
 		return this.defaultPostProcessor.postProcessResult(mi, decision);

+ 1 - 1
core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java

@@ -159,7 +159,7 @@ public final class AuthorizationManagerAfterReactiveMethodInterceptor implements
 			return Mono.just(methodInvocationResult.getResult());
 		}
 		return Mono.fromSupplier(() -> {
-			if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
+			if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
 				return postProcessableDecision.postProcessResult(methodInvocationResult, decision);
 			}
 			return this.defaultPostProcessor.postProcessResult(methodInvocationResult, decision);

+ 1 - 1
core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.java

@@ -257,7 +257,7 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
 	}
 
 	private Object handle(MethodInvocation mi, AuthorizationDecision decision) {
-		if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
+		if (decision instanceof MethodAuthorizationDeniedHandler handler) {
 			return handler.handle(mi, decision);
 		}
 		return this.defaultHandler.handle(mi, decision);

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

@@ -162,7 +162,7 @@ public final class AuthorizationManagerBeforeReactiveMethodInterceptor implement
 
 	private Mono<Object> postProcess(AuthorizationDecision decision, MethodInvocation mi) {
 		return Mono.fromSupplier(() -> {
-			if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
+			if (decision instanceof MethodAuthorizationDeniedHandler handler) {
 				return handler.handle(mi, decision);
 			}
 			return this.defaultHandler.handle(mi, decision);

+ 41 - 0
core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationDecision.java

@@ -0,0 +1,41 @@
+/*
+ * 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.expression.Expression;
+import org.springframework.security.authorization.AuthorizationResult;
+import org.springframework.security.authorization.ExpressionAuthorizationDecision;
+import org.springframework.util.Assert;
+
+class PostAuthorizeAuthorizationDecision extends ExpressionAuthorizationDecision
+		implements MethodAuthorizationDeniedPostProcessor {
+
+	private final MethodAuthorizationDeniedPostProcessor postProcessor;
+
+	PostAuthorizeAuthorizationDecision(boolean granted, Expression expression,
+			MethodAuthorizationDeniedPostProcessor postProcessor) {
+		super(granted, expression);
+		Assert.notNull(postProcessor, "postProcessor cannot be null");
+		this.postProcessor = postProcessor;
+	}
+
+	@Override
+	public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult result) {
+		return this.postProcessor.postProcessResult(methodInvocationResult, result);
+	}
+
+}

+ 5 - 12
core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java

@@ -27,7 +27,6 @@ import org.springframework.security.access.expression.method.MethodSecurityExpre
 import org.springframework.security.access.prepost.PostAuthorize;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationManager;
-import org.springframework.security.authorization.AuthorizationResult;
 import org.springframework.security.core.Authentication;
 
 /**
@@ -38,8 +37,7 @@ import org.springframework.security.core.Authentication;
  * @author Evgeniy Cheban
  * @since 5.6
  */
-public final class PostAuthorizeAuthorizationManager
-		implements AuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
+public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
 
 	private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
 
@@ -90,18 +88,13 @@ public final class PostAuthorizeAuthorizationManager
 		if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
 			return null;
 		}
+		PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
 		MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
 		EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi.getMethodInvocation());
 		expressionHandler.setReturnObject(mi.getResult(), ctx);
-		return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
-	}
-
-	@Override
-	public Object postProcessResult(MethodInvocationResult methodInvocationResult,
-			AuthorizationResult authorizationResult) {
-		ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
-		PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
-		return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult);
+		boolean granted = ExpressionUtils.evaluateAsBoolean(postAuthorizeAttribute.getExpression(), ctx);
+		return new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(),
+				postAuthorizeAttribute.getPostProcessor());
 	}
 
 }

+ 4 - 13
core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java

@@ -24,7 +24,6 @@ import org.springframework.security.access.expression.method.DefaultMethodSecuri
 import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
 import org.springframework.security.access.prepost.PostAuthorize;
 import org.springframework.security.authorization.AuthorizationDecision;
-import org.springframework.security.authorization.AuthorizationResult;
 import org.springframework.security.authorization.ReactiveAuthorizationManager;
 import org.springframework.security.core.Authentication;
 import org.springframework.util.Assert;
@@ -38,7 +37,7 @@ import org.springframework.util.Assert;
  * @since 5.8
  */
 public final class PostAuthorizeReactiveAuthorizationManager
-		implements ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
+		implements ReactiveAuthorizationManager<MethodInvocationResult> {
 
 	private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
 
@@ -83,23 +82,15 @@ public final class PostAuthorizeReactiveAuthorizationManager
 		if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
 			return Mono.empty();
 		}
-
+		PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
 		MethodSecurityExpressionHandler expressionHandler = this.registry.getExpressionHandler();
 		// @formatter:off
 		return authentication
 				.map((auth) -> expressionHandler.createEvaluationContext(auth, mi))
 				.doOnNext((ctx) -> expressionHandler.setReturnObject(result.getResult(), ctx))
-				.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
-				.cast(AuthorizationDecision.class);
+				.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
+				.map((granted) -> new PostAuthorizeAuthorizationDecision(granted, postAuthorizeAttribute.getExpression(), postAuthorizeAttribute.getPostProcessor()));
 		// @formatter:on
 	}
 
-	@Override
-	public Object postProcessResult(MethodInvocationResult methodInvocationResult,
-			AuthorizationResult authorizationResult) {
-		ExpressionAttribute attribute = this.registry.getAttribute(methodInvocationResult.getMethodInvocation());
-		PostAuthorizeExpressionAttribute postAuthorizeAttribute = (PostAuthorizeExpressionAttribute) attribute;
-		return postAuthorizeAttribute.getPostProcessor().postProcessResult(methodInvocationResult, authorizationResult);
-	}
-
 }

+ 43 - 0
core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationDecision.java

@@ -0,0 +1,43 @@
+/*
+ * 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.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.expression.Expression;
+import org.springframework.security.authorization.AuthorizationResult;
+import org.springframework.security.authorization.ExpressionAuthorizationDecision;
+import org.springframework.util.Assert;
+
+class PreAuthorizeAuthorizationDecision extends ExpressionAuthorizationDecision
+		implements MethodAuthorizationDeniedHandler {
+
+	private final MethodAuthorizationDeniedHandler handler;
+
+	PreAuthorizeAuthorizationDecision(boolean granted, Expression expression,
+			MethodAuthorizationDeniedHandler handler) {
+		super(granted, expression);
+		Assert.notNull(handler, "handler cannot be null");
+		this.handler = handler;
+	}
+
+	@Override
+	public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
+		return this.handler.handle(methodInvocation, result);
+	}
+
+}

+ 5 - 11
core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java

@@ -27,7 +27,6 @@ import org.springframework.security.access.expression.method.MethodSecurityExpre
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationManager;
-import org.springframework.security.authorization.AuthorizationResult;
 import org.springframework.security.core.Authentication;
 
 /**
@@ -38,8 +37,7 @@ import org.springframework.security.core.Authentication;
  * @author Evgeniy Cheban
  * @since 5.6
  */
-public final class PreAuthorizeAuthorizationManager
-		implements AuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
+public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
 
 	private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
 
@@ -82,15 +80,11 @@ public final class PreAuthorizeAuthorizationManager
 		if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
 			return null;
 		}
+		PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
 		EvaluationContext ctx = this.registry.getExpressionHandler().createEvaluationContext(authentication, mi);
-		return (AuthorizationDecision) ExpressionUtils.evaluate(attribute.getExpression(), ctx);
-	}
-
-	@Override
-	public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
-		ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
-		PreAuthorizeExpressionAttribute postAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
-		return postAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult);
+		boolean granted = ExpressionUtils.evaluateAsBoolean(preAuthorizeAttribute.getExpression(), ctx);
+		return new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(),
+				preAuthorizeAttribute.getHandler());
 	}
 
 }

+ 4 - 12
core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java

@@ -24,7 +24,6 @@ import org.springframework.security.access.expression.method.DefaultMethodSecuri
 import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.authorization.AuthorizationDecision;
-import org.springframework.security.authorization.AuthorizationResult;
 import org.springframework.security.authorization.ReactiveAuthorizationManager;
 import org.springframework.security.core.Authentication;
 import org.springframework.util.Assert;
@@ -37,8 +36,7 @@ import org.springframework.util.Assert;
  * @author Evgeniy Cheban
  * @since 5.8
  */
-public final class PreAuthorizeReactiveAuthorizationManager
-		implements ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
+public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
 
 	private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
 
@@ -81,19 +79,13 @@ public final class PreAuthorizeReactiveAuthorizationManager
 		if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
 			return Mono.empty();
 		}
+		PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
 		// @formatter:off
 		return authentication
 				.map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi))
-				.flatMap((ctx) -> ReactiveExpressionUtils.evaluate(attribute.getExpression(), ctx))
-				.cast(AuthorizationDecision.class);
+				.flatMap((ctx) -> ReactiveExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx))
+				.map((granted) -> new PreAuthorizeAuthorizationDecision(granted, preAuthorizeAttribute.getExpression(), preAuthorizeAttribute.getHandler()));
 		// @formatter:on
 	}
 
-	@Override
-	public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
-		ExpressionAttribute attribute = this.registry.getAttribute(methodInvocation);
-		PreAuthorizeExpressionAttribute preAuthorizeAttribute = (PreAuthorizeExpressionAttribute) attribute;
-		return preAuthorizeAttribute.getHandler().handle(methodInvocation, authorizationResult);
-	}
-
 }

+ 3 - 32
core/src/main/java/org/springframework/security/authorization/method/ReactiveExpressionUtils.java

@@ -21,8 +21,6 @@ import reactor.core.publisher.Mono;
 import org.springframework.expression.EvaluationContext;
 import org.springframework.expression.EvaluationException;
 import org.springframework.expression.Expression;
-import org.springframework.security.authorization.AuthorizationResult;
-import org.springframework.security.authorization.ExpressionAuthorizationDecision;
 
 /**
  * For internal use only, as this contract is likely to change.
@@ -32,33 +30,6 @@ import org.springframework.security.authorization.ExpressionAuthorizationDecisio
  */
 final class ReactiveExpressionUtils {
 
-	static Mono<AuthorizationResult> evaluate(Expression expr, EvaluationContext ctx) {
-		return Mono.defer(() -> {
-			Object value;
-			try {
-				value = expr.getValue(ctx);
-			}
-			catch (EvaluationException ex) {
-				return Mono.error(() -> new IllegalArgumentException(
-						"Failed to evaluate expression '" + expr.getExpressionString() + "'", ex));
-			}
-			if (value instanceof Mono<?> mono) {
-				return mono.flatMap((data) -> adapt(expr, data));
-			}
-			return adapt(expr, value);
-		});
-	}
-
-	private static Mono<AuthorizationResult> adapt(Expression expr, Object value) {
-		if (value instanceof Boolean granted) {
-			return Mono.just(new ExpressionAuthorizationDecision(granted, expr));
-		}
-		if (value instanceof AuthorizationResult decision) {
-			return Mono.just(decision);
-		}
-		return createInvalidReturnTypeMono(expr);
-	}
-
 	static Mono<Boolean> evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
 		return Mono.defer(() -> {
 			Object value;
@@ -85,9 +56,9 @@ final class ReactiveExpressionUtils {
 		});
 	}
 
-	private static <T> Mono<T> createInvalidReturnTypeMono(Expression expr) {
-		return Mono.error(() -> new IllegalStateException("Expression: '" + expr.getExpressionString()
-				+ "' must return boolean, Mono<Boolean>, AuthorizationResult, or Mono<AuthorizationResult>"));
+	private static Mono<Boolean> createInvalidReturnTypeMono(Expression expr) {
+		return Mono.error(() -> new IllegalStateException(
+				"Expression: '" + expr.getExpressionString() + "' must return boolean or Mono<Boolean>"));
 	}
 
 	private ReactiveExpressionUtils() {

+ 0 - 70
core/src/test/java/org/springframework/security/access/expression/ExpressionUtilsTests.java

@@ -1,70 +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.access.expression;
-
-import org.junit.jupiter.api.Test;
-
-import org.springframework.expression.Expression;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
-import org.springframework.expression.spel.support.StandardEvaluationContext;
-import org.springframework.security.authorization.AuthorizationDecision;
-import org.springframework.security.authorization.ExpressionAuthorizationDecision;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ExpressionUtilsTests {
-
-	private final Object details = new Object();
-
-	@Test
-	public void evaluateWhenAuthorizationDecisionThenReturns() {
-		SpelExpressionParser parser = new SpelExpressionParser();
-		Expression expression = parser.parseExpression("#root.returnDecision()");
-		StandardEvaluationContext context = new StandardEvaluationContext(this);
-		assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(AuthorizationDecisionDetails.class)
-			.extracting("details")
-			.isEqualTo(this.details);
-	}
-
-	@Test
-	public void evaluateWhenBooleanThenReturnsExpressionAuthorizationDecision() {
-		SpelExpressionParser parser = new SpelExpressionParser();
-		Expression expression = parser.parseExpression("#root.returnResult()");
-		StandardEvaluationContext context = new StandardEvaluationContext(this);
-		assertThat(ExpressionUtils.evaluate(expression, context)).isInstanceOf(ExpressionAuthorizationDecision.class);
-	}
-
-	public AuthorizationDecision returnDecision() {
-		return new AuthorizationDecisionDetails(false, this.details);
-	}
-
-	public boolean returnResult() {
-		return false;
-	}
-
-	static final class AuthorizationDecisionDetails extends AuthorizationDecision {
-
-		final Object details;
-
-		AuthorizationDecisionDetails(boolean granted, Object details) {
-			super(granted);
-			this.details = details;
-		}
-
-	}
-
-}

+ 52 - 33
core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java

@@ -19,15 +19,16 @@ package org.springframework.security.authorization.method;
 import org.aopalliance.intercept.MethodInvocation;
 import org.assertj.core.api.InstanceOfAssertFactories;
 import org.junit.jupiter.api.Test;
-import org.mockito.invocation.InvocationOnMock;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 import org.springframework.aop.Pointcut;
+import org.springframework.expression.common.LiteralExpression;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.access.intercept.method.MockMethodInvocation;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationDeniedException;
+import org.springframework.security.authorization.AuthorizationResult;
 import org.springframework.security.authorization.ReactiveAuthorizationManager;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -124,10 +125,10 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
 		MethodInvocation mockMethodInvocation = spy(
 				new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
 		given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
-		HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
-				HandlingReactiveAuthorizationManager.class);
-		given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
-		given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
+		ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
+				ReactiveAuthorizationManager.class);
+		given(mockReactiveAuthorizationManager.check(any(), any()))
+			.will((invocation) -> Mono.just(createDecision(new MaskingPostProcessor())));
 		AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
 				Pointcut.TRUE, mockReactiveAuthorizationManager);
 		Object result = interceptor.invoke(mockMethodInvocation);
@@ -143,16 +144,15 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
 		MethodInvocation mockMethodInvocation = spy(
 				new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
 		given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
-		HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
-				HandlingReactiveAuthorizationManager.class);
-		given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer((invocation) -> {
-			MethodInvocationResult argument = invocation.getArgument(0);
-			if (!"john".equals(argument.getResult())) {
-				return monoMasking(invocation);
+		ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
+				ReactiveAuthorizationManager.class);
+		given(mockReactiveAuthorizationManager.check(any(), any())).willAnswer((invocation) -> {
+			MethodInvocationResult argument = invocation.getArgument(1);
+			if ("john".equals(argument.getResult())) {
+				return Mono.just(new AuthorizationDecision(true));
 			}
-			return Mono.just(argument.getResult());
+			return Mono.just(createDecision(new MaskingPostProcessor()));
 		});
-		given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
 		AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
 				Pointcut.TRUE, mockReactiveAuthorizationManager);
 		Object result = interceptor.invoke(mockMethodInvocation);
@@ -168,10 +168,11 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
 		MethodInvocation mockMethodInvocation = spy(
 				new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
 		given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
-		HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
-				HandlingReactiveAuthorizationManager.class);
-		given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::masking);
-		given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
+		ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
+				ReactiveAuthorizationManager.class);
+		PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
+				new LiteralExpression("1234"), new MaskingPostProcessor());
+		given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
 		AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
 				Pointcut.TRUE, mockReactiveAuthorizationManager);
 		Object result = interceptor.invoke(mockMethodInvocation);
@@ -186,10 +187,11 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
 		MethodInvocation mockMethodInvocation = spy(
 				new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
 		given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
-		HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
-				HandlingReactiveAuthorizationManager.class);
-		given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willAnswer(this::monoMasking);
-		given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
+		ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
+				ReactiveAuthorizationManager.class);
+		PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
+				new LiteralExpression("1234"), new MonoMaskingPostProcessor());
+		given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
 		AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
 				Pointcut.TRUE, mockReactiveAuthorizationManager);
 		Object result = interceptor.invoke(mockMethodInvocation);
@@ -204,10 +206,11 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
 		MethodInvocation mockMethodInvocation = spy(
 				new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
 		given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
-		HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
-				HandlingReactiveAuthorizationManager.class);
-		given(mockReactiveAuthorizationManager.postProcessResult(any(), any())).willReturn(null);
-		given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.empty());
+		ReactiveAuthorizationManager<MethodInvocationResult> mockReactiveAuthorizationManager = mock(
+				ReactiveAuthorizationManager.class);
+		PostAuthorizeAuthorizationDecision decision = new PostAuthorizeAuthorizationDecision(false,
+				new LiteralExpression("1234"), new NullPostProcessor());
+		given(mockReactiveAuthorizationManager.check(any(), any())).willReturn(Mono.just(decision));
 		AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor(
 				Pointcut.TRUE, mockReactiveAuthorizationManager);
 		Object result = interceptor.invoke(mockMethodInvocation);
@@ -235,18 +238,34 @@ public class AuthorizationManagerAfterReactiveMethodInterceptorTests {
 		verify(mockReactiveAuthorizationManager).check(any(), any());
 	}
 
-	private Object masking(InvocationOnMock invocation) {
-		MethodInvocationResult result = invocation.getArgument(0);
-		return result.getResult() + "-masked";
+	private PostAuthorizeAuthorizationDecision createDecision(MethodAuthorizationDeniedPostProcessor postProcessor) {
+		return new PostAuthorizeAuthorizationDecision(false, new LiteralExpression("1234"), postProcessor);
 	}
 
-	private Object monoMasking(InvocationOnMock invocation) {
-		MethodInvocationResult result = invocation.getArgument(0);
-		return Mono.just(result.getResult() + "-masked");
+	static class MaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
+
+		@Override
+		public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
+			return contextObject.getResult() + "-masked";
+		}
+
 	}
 
-	interface HandlingReactiveAuthorizationManager
-			extends ReactiveAuthorizationManager<MethodInvocationResult>, MethodAuthorizationDeniedPostProcessor {
+	static class MonoMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor {
+
+		@Override
+		public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
+			return Mono.just(contextObject.getResult() + "-masked");
+		}
+
+	}
+
+	static class NullPostProcessor implements MethodAuthorizationDeniedPostProcessor {
+
+		@Override
+		public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) {
+			return null;
+		}
 
 	}
 

+ 32 - 14
core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java

@@ -23,10 +23,12 @@ import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 import org.springframework.aop.Pointcut;
+import org.springframework.expression.common.LiteralExpression;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.access.intercept.method.MockMethodInvocation;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationDeniedException;
+import org.springframework.security.authorization.AuthorizationResult;
 import org.springframework.security.authorization.ReactiveAuthorizationManager;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -123,10 +125,11 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
 		MethodInvocation mockMethodInvocation = spy(
 				new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
 		given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
-		HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
-				HandlingReactiveAuthorizationManager.class);
-		given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
-		given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn("***");
+		ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
+				ReactiveAuthorizationManager.class);
+		PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
+				new LiteralExpression("1234"), new MaskingPostProcessor());
+		given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
 		AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
 				Pointcut.TRUE, mockReactiveAuthorizationManager);
 		Object result = interceptor.invoke(mockMethodInvocation);
@@ -141,10 +144,11 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
 		MethodInvocation mockMethodInvocation = spy(
 				new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono")));
 		given(mockMethodInvocation.proceed()).willReturn(Mono.just("john"));
-		HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
-				HandlingReactiveAuthorizationManager.class);
-		given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
-		given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
+		ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
+				ReactiveAuthorizationManager.class);
+		PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
+				new LiteralExpression("1234"), new MonoMaskingPostProcessor());
+		given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
 		AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
 				Pointcut.TRUE, mockReactiveAuthorizationManager);
 		Object result = interceptor.invoke(mockMethodInvocation);
@@ -159,10 +163,11 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
 		MethodInvocation mockMethodInvocation = spy(
 				new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux")));
 		given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob"));
-		HandlingReactiveAuthorizationManager mockReactiveAuthorizationManager = mock(
-				HandlingReactiveAuthorizationManager.class);
-		given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.empty());
-		given(mockReactiveAuthorizationManager.handle(any(), any())).willReturn(Mono.just("***"));
+		ReactiveAuthorizationManager<MethodInvocation> mockReactiveAuthorizationManager = mock(
+				ReactiveAuthorizationManager.class);
+		PreAuthorizeAuthorizationDecision decision = new PreAuthorizeAuthorizationDecision(false,
+				new LiteralExpression("1234"), new MonoMaskingPostProcessor());
+		given(mockReactiveAuthorizationManager.check(any(), eq(mockMethodInvocation))).willReturn(Mono.just(decision));
 		AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor(
 				Pointcut.TRUE, mockReactiveAuthorizationManager);
 		Object result = interceptor.invoke(mockMethodInvocation);
@@ -209,8 +214,21 @@ public class AuthorizationManagerBeforeReactiveMethodInterceptorTests {
 		verify(mockReactiveAuthorizationManager).check(any(), eq(mockMethodInvocation));
 	}
 
-	interface HandlingReactiveAuthorizationManager
-			extends ReactiveAuthorizationManager<MethodInvocation>, MethodAuthorizationDeniedHandler {
+	static class MaskingPostProcessor implements MethodAuthorizationDeniedHandler {
+
+		@Override
+		public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
+			return "***";
+		}
+
+	}
+
+	static class MonoMaskingPostProcessor implements MethodAuthorizationDeniedHandler {
+
+		@Override
+		public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) {
+			return Mono.just("***");
+		}
 
 	}
 

+ 0 - 36
docs/modules/ROOT/pages/servlet/authorization/method-security.adoc

@@ -1215,42 +1215,6 @@ Spring Security will invoke the given method on that bean for each method invoca
 What's nice about this is all your authorization logic is in a separate class that can be independently unit tested and verified for correctness.
 It also has access to the full Java language.
 
-[TIP]
-In addition to returning a `Boolean`, you can also return `null` to indicate that the code abstains from making a decision.
-
-If you want to include more information about the nature of the decision, you can instead return a custom `AuthorizationDecision` like this:
-
-[tabs]
-======
-Java::
-+
-[source,java,role="primary"]
-----
-@Component("authz")
-public class AuthorizationLogic {
-    public AuthorizationDecision decide(MethodSecurityExpressionOperations operations) {
-        // ... authorization logic
-        return new MyAuthorizationDecision(false, details);
-    }
-}
-----
-
-Kotlin::
-+
-[source,kotlin,role="secondary"]
-----
-@Component("authz")
-open class AuthorizationLogic {
-    fun decide(val operations: MethodSecurityExpressionOperations): AuthorizationDecision {
-        // ... authorization logic
-        return MyAuthorizationDecision(false, details)
-    }
-}
-----
-======
-
-Then, you can access the custom details when you <<fallback-values-authorization-denied, customize how the authorization result is handled>>.
-
 [[custom-authorization-managers]]
 === Using a Custom Authorization Manager