2
0
Эх сурвалжийг харах

Add Value-Type Ignore Support

Issue gh-14597
Josh Cummings 1 жил өмнө
parent
commit
795e44d11f

+ 5 - 2
config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyConfiguration.java

@@ -30,16 +30,19 @@ import org.springframework.context.annotation.Role;
 import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory;
 import org.springframework.security.authorization.method.AuthorizationAdvisor;
 import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;
+import org.springframework.security.config.Customizer;
 
 @Configuration(proxyBeanMethods = false)
 final class AuthorizationProxyConfiguration implements AopInfrastructureBean {
 
 	@Bean
 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
-	static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider<AuthorizationAdvisor> provider) {
+	static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider<AuthorizationAdvisor> provider,
+			ObjectProvider<Customizer<AuthorizationAdvisorProxyFactory>> customizers) {
 		List<AuthorizationAdvisor> advisors = new ArrayList<>();
 		provider.forEach(advisors::add);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
+		customizers.forEach((c) -> c.customize(factory));
 		factory.setAdvisors(advisors);
 		return factory;
 	}

+ 7 - 5
config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationProxyConfiguration.java

@@ -27,20 +27,22 @@ import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Role;
-import org.springframework.security.authorization.ReactiveAuthorizationAdvisorProxyFactory;
+import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory;
 import org.springframework.security.authorization.method.AuthorizationAdvisor;
 import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;
+import org.springframework.security.config.Customizer;
 
 @Configuration(proxyBeanMethods = false)
 final class ReactiveAuthorizationProxyConfiguration implements AopInfrastructureBean {
 
 	@Bean
 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
-	static ReactiveAuthorizationAdvisorProxyFactory authorizationProxyFactory(
-			ObjectProvider<AuthorizationAdvisor> provider) {
+	static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider<AuthorizationAdvisor> provider,
+			ObjectProvider<Customizer<AuthorizationAdvisorProxyFactory>> customizers) {
 		List<AuthorizationAdvisor> advisors = new ArrayList<>();
 		provider.forEach(advisors::add);
-		ReactiveAuthorizationAdvisorProxyFactory factory = new ReactiveAuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
+		customizers.forEach((c) -> c.customize(factory));
 		factory.setAdvisors(advisors);
 		return factory;
 	}
@@ -48,7 +50,7 @@ final class ReactiveAuthorizationProxyConfiguration implements AopInfrastructure
 	@Bean
 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 	static MethodInterceptor authorizeReturnObjectMethodInterceptor(ObjectProvider<AuthorizationAdvisor> provider,
-			ReactiveAuthorizationAdvisorProxyFactory authorizationProxyFactory) {
+			AuthorizationAdvisorProxyFactory authorizationProxyFactory) {
 		AuthorizeReturnObjectMethodInterceptor interceptor = new AuthorizeReturnObjectMethodInterceptor(
 				authorizationProxyFactory);
 		List<AuthorizationAdvisor> advisors = new ArrayList<>();

+ 10 - 1
config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

@@ -58,6 +58,8 @@ import org.springframework.security.access.prepost.PostAuthorize;
 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.AuthorizationAdvisorProxyFactory;
+import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory.TargetVisitor;
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.AuthorizationEventPublisher;
 import org.springframework.security.authorization.AuthorizationManager;
@@ -66,6 +68,7 @@ import org.springframework.security.authorization.method.AuthorizationManagerBef
 import org.springframework.security.authorization.method.AuthorizeReturnObject;
 import org.springframework.security.authorization.method.MethodInvocationResult;
 import org.springframework.security.authorization.method.PrePostTemplateDefaults;
+import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
 import org.springframework.security.config.core.GrantedAuthorityDefaults;
 import org.springframework.security.config.test.SpringTestContext;
@@ -1143,6 +1146,12 @@ public class PrePostMethodSecurityConfigurationTests {
 	@Configuration
 	static class AuthorizeResultConfig {
 
+		@Bean
+		@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+		static Customizer<AuthorizationAdvisorProxyFactory> skipValueTypes() {
+			return (f) -> f.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
+		}
+
 		@Bean
 		FlightRepository flights() {
 			FlightRepository flights = new FlightRepository();
@@ -1186,6 +1195,7 @@ public class PrePostMethodSecurityConfigurationTests {
 
 	}
 
+	@AuthorizeReturnObject
 	static class Flight {
 
 		private final String id;
@@ -1216,7 +1226,6 @@ public class PrePostMethodSecurityConfigurationTests {
 			return this.seats;
 		}
 
-		@AuthorizeReturnObject
 		@PostAuthorize("hasAuthority('seating:read')")
 		@PostFilter("filterObject.name != 'Kevin Mitnick'")
 		List<Passenger> getPassengers() {

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

@@ -30,8 +30,10 @@ import reactor.core.publisher.Mono;
 import reactor.test.StepVerifier;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Role;
 import org.springframework.expression.EvaluationContext;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.access.expression.SecurityExpressionRoot;
@@ -42,7 +44,10 @@ 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.authentication.TestAuthentication;
+import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory;
+import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory.TargetVisitor;
 import org.springframework.security.authorization.method.AuthorizeReturnObject;
+import org.springframework.security.config.Customizer;
 import org.springframework.security.config.core.GrantedAuthorityDefaults;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
@@ -238,6 +243,12 @@ public class ReactiveMethodSecurityConfigurationTests {
 	@Configuration
 	static class AuthorizeResultConfig {
 
+		@Bean
+		@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+		static Customizer<AuthorizationAdvisorProxyFactory> skipValueTypes() {
+			return (factory) -> factory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
+		}
+
 		@Bean
 		FlightRepository flights() {
 			FlightRepository flights = new FlightRepository();
@@ -282,6 +293,7 @@ public class ReactiveMethodSecurityConfigurationTests {
 
 	}
 
+	@AuthorizeReturnObject
 	static class Flight {
 
 		private final String id;
@@ -312,7 +324,6 @@ public class ReactiveMethodSecurityConfigurationTests {
 			return Mono.just(this.seats);
 		}
 
-		@AuthorizeReturnObject
 		@PostAuthorize("hasAnyAuthority('seating:read', 'airplane:read')")
 		@PostFilter("@isNotKevin.apply(filterObject)")
 		Flux<Passenger> getPassengers() {

+ 334 - 144
core/src/main/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactory.java

@@ -34,17 +34,28 @@ import java.util.SortedMap;
 import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.function.Supplier;
 import java.util.stream.Stream;
 
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
 import org.springframework.aop.Advisor;
 import org.springframework.aop.framework.ProxyFactory;
 import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.lang.NonNull;
 import org.springframework.security.authorization.method.AuthorizationAdvisor;
 import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
+import org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor;
 import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
+import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor;
+import org.springframework.security.authorization.method.AuthorizeReturnObject;
 import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;
 import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
+import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor;
 import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
+import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
+import org.springframework.util.Assert;
 import org.springframework.util.ClassUtils;
 
 /**
@@ -63,8 +74,7 @@ import org.springframework.util.ClassUtils;
  * like so:
  *
  * <pre>
- *     AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
- *     AuthorizationProxyFactory proxyFactory = new AuthorizationProxyFactory(preAuthorize);
+ *     AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
  *     Foo foo = new Foo();
  *     foo.bar(); // passes
  *     Foo securedFoo = proxyFactory.proxy(foo);
@@ -74,18 +84,56 @@ import org.springframework.util.ClassUtils;
  * @author Josh Cummings
  * @since 6.3
  */
-public final class AuthorizationAdvisorProxyFactory implements AuthorizationProxyFactory {
+public final class AuthorizationAdvisorProxyFactory
+		implements AuthorizationProxyFactory, Iterable<AuthorizationAdvisor> {
+
+	private static final boolean isReactivePresent = ClassUtils.isPresent("reactor.core.publisher.Mono", null);
+
+	private static final TargetVisitor DEFAULT_VISITOR = isReactivePresent
+			? TargetVisitor.of(new ClassVisitor(), new ReactiveTypeVisitor(), new ContainerTypeVisitor())
+			: TargetVisitor.of(new ClassVisitor(), new ContainerTypeVisitor());
+
+	private static final TargetVisitor DEFAULT_VISITOR_SKIP_VALUE_TYPES = TargetVisitor.of(new ClassVisitor(),
+			new IgnoreValueTypeVisitor(), DEFAULT_VISITOR);
 
-	private List<AuthorizationAdvisor> advisors = new ArrayList<>();
+	private List<AuthorizationAdvisor> advisors;
+
+	private TargetVisitor visitor = DEFAULT_VISITOR;
+
+	private AuthorizationAdvisorProxyFactory(List<AuthorizationAdvisor> advisors) {
+		this.advisors = new ArrayList<>(advisors);
+		this.advisors.add(new AuthorizeReturnObjectMethodInterceptor(this));
+		setAdvisors(this.advisors);
+	}
 
-	public AuthorizationAdvisorProxyFactory() {
+	/**
+	 * Construct an {@link AuthorizationAdvisorProxyFactory} with the defaults needed for
+	 * wrapping objects in Spring Security's pre-post method security support.
+	 * @return an {@link AuthorizationAdvisorProxyFactory} for adding pre-post method
+	 * security support
+	 */
+	public static AuthorizationAdvisorProxyFactory withDefaults() {
 		List<AuthorizationAdvisor> advisors = new ArrayList<>();
 		advisors.add(AuthorizationManagerBeforeMethodInterceptor.preAuthorize());
 		advisors.add(AuthorizationManagerAfterMethodInterceptor.postAuthorize());
 		advisors.add(new PreFilterAuthorizationMethodInterceptor());
 		advisors.add(new PostFilterAuthorizationMethodInterceptor());
-		advisors.add(new AuthorizeReturnObjectMethodInterceptor(this));
-		setAdvisors(advisors);
+		return new AuthorizationAdvisorProxyFactory(advisors);
+	}
+
+	/**
+	 * Construct an {@link AuthorizationAdvisorProxyFactory} with the defaults needed for
+	 * wrapping objects in Spring Security's pre-post reactive method security support.
+	 * @return an {@link AuthorizationAdvisorProxyFactory} for adding pre-post reactive
+	 * method security support
+	 */
+	public static AuthorizationAdvisorProxyFactory withReactiveDefaults() {
+		List<AuthorizationAdvisor> advisors = new ArrayList<>();
+		advisors.add(AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize());
+		advisors.add(AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize());
+		advisors.add(new PreFilterAuthorizationReactiveMethodInterceptor());
+		advisors.add(new PostFilterAuthorizationReactiveMethodInterceptor());
+		return new AuthorizationAdvisorProxyFactory(advisors);
 	}
 
 	/**
@@ -111,41 +159,9 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
 		if (target == null) {
 			return null;
 		}
-		if (target instanceof Class<?> targetClass) {
-			return proxyClass(targetClass);
-		}
-		if (target instanceof Iterator<?> iterator) {
-			return proxyIterator(iterator);
-		}
-		if (target instanceof Queue<?> queue) {
-			return proxyQueue(queue);
-		}
-		if (target instanceof List<?> list) {
-			return proxyList(list);
-		}
-		if (target instanceof SortedSet<?> set) {
-			return proxySortedSet(set);
-		}
-		if (target instanceof Set<?> set) {
-			return proxySet(set);
-		}
-		if (target.getClass().isArray()) {
-			return proxyArray((Object[]) target);
-		}
-		if (target instanceof SortedMap<?, ?> map) {
-			return proxySortedMap(map);
-		}
-		if (target instanceof Iterable<?> iterable) {
-			return proxyIterable(iterable);
-		}
-		if (target instanceof Map<?, ?> map) {
-			return proxyMap(map);
-		}
-		if (target instanceof Stream<?> stream) {
-			return proxyStream(stream);
-		}
-		if (target instanceof Optional<?> optional) {
-			return proxyOptional(optional);
+		Object proxied = this.visitor.visit(this, target);
+		if (proxied != null) {
+			return proxied;
 		}
 		ProxyFactory factory = new ProxyFactory(target);
 		for (Advisor advisor : this.advisors) {
@@ -179,144 +195,318 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
 		AnnotationAwareOrderComparator.sort(this.advisors);
 	}
 
-	@SuppressWarnings("unchecked")
-	private <T> T proxyCast(T target) {
-		return (T) proxy(target);
+	/**
+	 * Use this visitor to navigate the proxy target's hierarchy.
+	 *
+	 * <p>
+	 * This can be helpful when you want a specialized behavior for a type or set of
+	 * types. For example, if you want to have this factory skip primitives and wrappers,
+	 * then you can do:
+	 *
+	 * <pre>
+	 * 	AuthorizationAdvisorProxyFactory proxyFactory = new AuthorizationAdvisorProxyFactory();
+	 * 	proxyFactory.setTargetVisitor(AuthorizationAdvisorProxyFactory.DEFAULT_VISITOR_IGNORE_VALUE_TYPES);
+	 * </pre>
+	 * @param visitor the visitor to use to introduce specialized behavior for a type
+	 */
+	public void setTargetVisitor(TargetVisitor visitor) {
+		Assert.notNull(visitor, "delegate cannot be null");
+		this.visitor = visitor;
 	}
 
-	private Class<?> proxyClass(Class<?> targetClass) {
-		ProxyFactory factory = new ProxyFactory();
-		factory.setTargetClass(targetClass);
-		factory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass));
-		factory.setProxyTargetClass(!Modifier.isFinal(targetClass.getModifiers()));
-		for (Advisor advisor : this.advisors) {
-			factory.addAdvisors(advisor);
-		}
-		return factory.getProxyClass(getClass().getClassLoader());
+	@Override
+	@NonNull
+	public Iterator<AuthorizationAdvisor> iterator() {
+		return this.advisors.iterator();
 	}
 
-	private <T> Iterable<T> proxyIterable(Iterable<T> iterable) {
-		return () -> proxyIterator(iterable.iterator());
+	/**
+	 * An interface to handle how the {@link AuthorizationAdvisorProxyFactory} should step
+	 * through the target's object hierarchy.
+	 *
+	 * @author Josh Cummings
+	 * @since 6.3
+	 * @see AuthorizationAdvisorProxyFactory#setTargetVisitor
+	 */
+	public interface TargetVisitor {
+
+		/**
+		 * Visit and possibly proxy this object.
+		 *
+		 * <p>
+		 * Visiting may take the form of walking down this object's hierarchy and proxying
+		 * sub-objects.
+		 *
+		 * <p>
+		 * An example is a visitor that proxies the elements of a {@link List} instead of
+		 * the list itself
+		 *
+		 * <p>
+		 * Returning {@code null} implies that this visitor does not want to proxy this
+		 * object
+		 * @param proxyFactory the proxy factory to delegate proxying to for any
+		 * sub-objects
+		 * @param target the object to proxy
+		 * @return the visited (and possibly proxied) object
+		 */
+		Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target);
+
+		/**
+		 * The default {@link TargetVisitor}, which will proxy {@link Class} instances as
+		 * well as instances contained in reactive types (if reactor is present),
+		 * collection types, and other container types like {@link Optional}
+		 */
+		static TargetVisitor defaults() {
+			return AuthorizationAdvisorProxyFactory.DEFAULT_VISITOR;
+		}
+
+		/**
+		 * The default {@link TargetVisitor} that also skips any value types (for example,
+		 * {@link String}, {@link Integer}). This is handy for annotations like
+		 * {@link AuthorizeReturnObject} when used at the class level
+		 */
+		static TargetVisitor defaultsSkipValueTypes() {
+			return AuthorizationAdvisorProxyFactory.DEFAULT_VISITOR_SKIP_VALUE_TYPES;
+		}
+
+		static TargetVisitor of(TargetVisitor... visitors) {
+			return (proxyFactory, target) -> {
+				for (TargetVisitor visitor : visitors) {
+					Object result = visitor.visit(proxyFactory, target);
+					if (result != null) {
+						return result;
+					}
+				}
+				return null;
+			};
+		}
+
 	}
 
-	private <T> Iterator<T> proxyIterator(Iterator<T> iterator) {
-		return new Iterator<>() {
-			@Override
-			public boolean hasNext() {
-				return iterator.hasNext();
-			}
+	private static final class IgnoreValueTypeVisitor implements TargetVisitor {
 
-			@Override
-			public T next() {
-				return proxyCast(iterator.next());
+		@Override
+		public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object object) {
+			if (ClassUtils.isSimpleValueType(object.getClass())) {
+				return object;
 			}
-		};
+			return null;
+		}
+
 	}
 
-	private <T> SortedSet<T> proxySortedSet(SortedSet<T> set) {
-		SortedSet<T> proxies = new TreeSet<>(set.comparator());
-		for (T toProxy : set) {
-			proxies.add(proxyCast(toProxy));
-		}
-		try {
-			set.clear();
-			set.addAll(proxies);
-			return proxies;
-		}
-		catch (UnsupportedOperationException ex) {
-			return Collections.unmodifiableSortedSet(proxies);
+	private static final class ClassVisitor implements TargetVisitor {
+
+		@Override
+		public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object object) {
+			if (object instanceof Class<?> targetClass) {
+				ProxyFactory factory = new ProxyFactory();
+				factory.setTargetClass(targetClass);
+				factory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass));
+				factory.setProxyTargetClass(!Modifier.isFinal(targetClass.getModifiers()));
+				for (Advisor advisor : proxyFactory) {
+					factory.addAdvisors(advisor);
+				}
+				return factory.getProxyClass(getClass().getClassLoader());
+			}
+			return null;
 		}
+
 	}
 
-	private <T> Set<T> proxySet(Set<T> set) {
-		Set<T> proxies = new LinkedHashSet<>(set.size());
-		for (T toProxy : set) {
-			proxies.add(proxyCast(toProxy));
+	private static final class ContainerTypeVisitor implements TargetVisitor {
+
+		@Override
+		public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) {
+			if (target instanceof Iterator<?> iterator) {
+				return proxyIterator(proxyFactory, iterator);
+			}
+			if (target instanceof Queue<?> queue) {
+				return proxyQueue(proxyFactory, queue);
+			}
+			if (target instanceof List<?> list) {
+				return proxyList(proxyFactory, list);
+			}
+			if (target instanceof SortedSet<?> set) {
+				return proxySortedSet(proxyFactory, set);
+			}
+			if (target instanceof Set<?> set) {
+				return proxySet(proxyFactory, set);
+			}
+			if (target.getClass().isArray()) {
+				return proxyArray(proxyFactory, (Object[]) target);
+			}
+			if (target instanceof SortedMap<?, ?> map) {
+				return proxySortedMap(proxyFactory, map);
+			}
+			if (target instanceof Iterable<?> iterable) {
+				return proxyIterable(proxyFactory, iterable);
+			}
+			if (target instanceof Map<?, ?> map) {
+				return proxyMap(proxyFactory, map);
+			}
+			if (target instanceof Stream<?> stream) {
+				return proxyStream(proxyFactory, stream);
+			}
+			if (target instanceof Optional<?> optional) {
+				return proxyOptional(proxyFactory, optional);
+			}
+			return null;
 		}
-		try {
-			set.clear();
-			set.addAll(proxies);
-			return proxies;
+
+		@SuppressWarnings("unchecked")
+		private <T> T proxyCast(AuthorizationProxyFactory proxyFactory, T target) {
+			return (T) proxyFactory.proxy(target);
 		}
-		catch (UnsupportedOperationException ex) {
-			return Collections.unmodifiableSet(proxies);
+
+		private <T> Iterable<T> proxyIterable(AuthorizationProxyFactory proxyFactory, Iterable<T> iterable) {
+			return () -> proxyIterator(proxyFactory, iterable.iterator());
 		}
-	}
 
-	private <T> Queue<T> proxyQueue(Queue<T> queue) {
-		Queue<T> proxies = new LinkedList<>();
-		for (T toProxy : queue) {
-			proxies.add(proxyCast(toProxy));
+		private <T> Iterator<T> proxyIterator(AuthorizationProxyFactory proxyFactory, Iterator<T> iterator) {
+			return new Iterator<>() {
+				@Override
+				public boolean hasNext() {
+					return iterator.hasNext();
+				}
+
+				@Override
+				public T next() {
+					return proxyCast(proxyFactory, iterator.next());
+				}
+			};
+		}
+
+		private <T> SortedSet<T> proxySortedSet(AuthorizationProxyFactory proxyFactory, SortedSet<T> set) {
+			SortedSet<T> proxies = new TreeSet<>(set.comparator());
+			for (T toProxy : set) {
+				proxies.add(proxyCast(proxyFactory, toProxy));
+			}
+			try {
+				set.clear();
+				set.addAll(proxies);
+				return proxies;
+			}
+			catch (UnsupportedOperationException ex) {
+				return Collections.unmodifiableSortedSet(proxies);
+			}
 		}
-		queue.clear();
-		queue.addAll(proxies);
-		return proxies;
-	}
 
-	private <T> List<T> proxyList(List<T> list) {
-		List<T> proxies = new ArrayList<>(list.size());
-		for (T toProxy : list) {
-			proxies.add(proxyCast(toProxy));
+		private <T> Set<T> proxySet(AuthorizationProxyFactory proxyFactory, Set<T> set) {
+			Set<T> proxies = new LinkedHashSet<>(set.size());
+			for (T toProxy : set) {
+				proxies.add(proxyCast(proxyFactory, toProxy));
+			}
+			try {
+				set.clear();
+				set.addAll(proxies);
+				return proxies;
+			}
+			catch (UnsupportedOperationException ex) {
+				return Collections.unmodifiableSet(proxies);
+			}
 		}
-		try {
-			list.clear();
-			list.addAll(proxies);
+
+		private <T> Queue<T> proxyQueue(AuthorizationProxyFactory proxyFactory, Queue<T> queue) {
+			Queue<T> proxies = new LinkedList<>();
+			for (T toProxy : queue) {
+				proxies.add(proxyCast(proxyFactory, toProxy));
+			}
+			queue.clear();
+			queue.addAll(proxies);
 			return proxies;
 		}
-		catch (UnsupportedOperationException ex) {
-			return Collections.unmodifiableList(proxies);
+
+		private <T> List<T> proxyList(AuthorizationProxyFactory proxyFactory, List<T> list) {
+			List<T> proxies = new ArrayList<>(list.size());
+			for (T toProxy : list) {
+				proxies.add(proxyCast(proxyFactory, toProxy));
+			}
+			try {
+				list.clear();
+				list.addAll(proxies);
+				return proxies;
+			}
+			catch (UnsupportedOperationException ex) {
+				return Collections.unmodifiableList(proxies);
+			}
 		}
-	}
 
-	private Object[] proxyArray(Object[] objects) {
-		List<Object> retain = new ArrayList<>(objects.length);
-		for (Object object : objects) {
-			retain.add(proxy(object));
+		private Object[] proxyArray(AuthorizationProxyFactory proxyFactory, Object[] objects) {
+			List<Object> retain = new ArrayList<>(objects.length);
+			for (Object object : objects) {
+				retain.add(proxyFactory.proxy(object));
+			}
+			Object[] proxies = (Object[]) Array.newInstance(objects.getClass().getComponentType(), retain.size());
+			for (int i = 0; i < retain.size(); i++) {
+				proxies[i] = retain.get(i);
+			}
+			return proxies;
 		}
-		Object[] proxies = (Object[]) Array.newInstance(objects.getClass().getComponentType(), retain.size());
-		for (int i = 0; i < retain.size(); i++) {
-			proxies[i] = retain.get(i);
+
+		private <K, V> SortedMap<K, V> proxySortedMap(AuthorizationProxyFactory proxyFactory, SortedMap<K, V> entries) {
+			SortedMap<K, V> proxies = new TreeMap<>(entries.comparator());
+			for (Map.Entry<K, V> entry : entries.entrySet()) {
+				proxies.put(entry.getKey(), proxyCast(proxyFactory, entry.getValue()));
+			}
+			try {
+				entries.clear();
+				entries.putAll(proxies);
+				return entries;
+			}
+			catch (UnsupportedOperationException ex) {
+				return Collections.unmodifiableSortedMap(proxies);
+			}
 		}
-		return proxies;
-	}
 
-	private <K, V> SortedMap<K, V> proxySortedMap(SortedMap<K, V> entries) {
-		SortedMap<K, V> proxies = new TreeMap<>(entries.comparator());
-		for (Map.Entry<K, V> entry : entries.entrySet()) {
-			proxies.put(entry.getKey(), proxyCast(entry.getValue()));
+		private <K, V> Map<K, V> proxyMap(AuthorizationProxyFactory proxyFactory, Map<K, V> entries) {
+			Map<K, V> proxies = new LinkedHashMap<>(entries.size());
+			for (Map.Entry<K, V> entry : entries.entrySet()) {
+				proxies.put(entry.getKey(), proxyCast(proxyFactory, entry.getValue()));
+			}
+			try {
+				entries.clear();
+				entries.putAll(proxies);
+				return entries;
+			}
+			catch (UnsupportedOperationException ex) {
+				return Collections.unmodifiableMap(proxies);
+			}
 		}
-		try {
-			entries.clear();
-			entries.putAll(proxies);
-			return entries;
+
+		private Stream<?> proxyStream(AuthorizationProxyFactory proxyFactory, Stream<?> stream) {
+			return stream.map(proxyFactory::proxy).onClose(stream::close);
 		}
-		catch (UnsupportedOperationException ex) {
-			return Collections.unmodifiableSortedMap(proxies);
+
+		@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+		private Optional<?> proxyOptional(AuthorizationProxyFactory proxyFactory, Optional<?> optional) {
+			return optional.map(proxyFactory::proxy);
 		}
+
 	}
 
-	private <K, V> Map<K, V> proxyMap(Map<K, V> entries) {
-		Map<K, V> proxies = new LinkedHashMap<>(entries.size());
-		for (Map.Entry<K, V> entry : entries.entrySet()) {
-			proxies.put(entry.getKey(), proxyCast(entry.getValue()));
-		}
-		try {
-			entries.clear();
-			entries.putAll(proxies);
-			return entries;
+	private static class ReactiveTypeVisitor implements TargetVisitor {
+
+		@Override
+		@SuppressWarnings("ReactiveStreamsUnusedPublisher")
+		public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) {
+			if (target instanceof Mono<?> mono) {
+				return proxyMono(proxyFactory, mono);
+			}
+			if (target instanceof Flux<?> flux) {
+				return proxyFlux(proxyFactory, flux);
+			}
+			return null;
 		}
-		catch (UnsupportedOperationException ex) {
-			return Collections.unmodifiableMap(proxies);
+
+		private Mono<?> proxyMono(AuthorizationProxyFactory proxyFactory, Mono<?> mono) {
+			return mono.map(proxyFactory::proxy);
 		}
-	}
 
-	private Stream<?> proxyStream(Stream<?> stream) {
-		return stream.map(this::proxy).onClose(stream::close);
-	}
+		private Flux<?> proxyFlux(AuthorizationProxyFactory proxyFactory, Flux<?> flux) {
+			return flux.map(proxyFactory::proxy);
+		}
 
-	@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
-	private Optional<?> proxyOptional(Optional<?> optional) {
-		return optional.map(this::proxy);
 	}
 
 }

+ 0 - 139
core/src/main/java/org/springframework/security/authorization/ReactiveAuthorizationAdvisorProxyFactory.java

@@ -1,139 +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;
-
-import java.lang.reflect.Array;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
-import org.springframework.aop.framework.ProxyFactory;
-import org.springframework.security.authorization.method.AuthorizationAdvisor;
-import org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor;
-import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor;
-import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;
-import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor;
-import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
-
-/**
- * A proxy factory for applying authorization advice to an arbitrary object.
- *
- * <p>
- * For example, consider a non-Spring-managed object {@code Foo}: <pre>
- *     class Foo {
- *         &#064;PreAuthorize("hasAuthority('bar:read')")
- *         String bar() { ... }
- *     }
- * </pre>
- *
- * Use {@link ReactiveAuthorizationAdvisorProxyFactory} to wrap the instance in Spring
- * Security's {@link org.springframework.security.access.prepost.PreAuthorize} method
- * interceptor like so:
- *
- * <pre>
- *     AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
- *     AuthorizationProxyFactory proxyFactory = new AuthorizationProxyFactory(preAuthorize);
- *     Foo foo = new Foo();
- *     foo.bar(); // passes
- *     Foo securedFoo = proxyFactory.proxy(foo);
- *     securedFoo.bar(); // access denied!
- * </pre>
- *
- * @author Josh Cummings
- * @since 6.3
- */
-public final class ReactiveAuthorizationAdvisorProxyFactory implements AuthorizationProxyFactory {
-
-	private final AuthorizationAdvisorProxyFactory defaults = new AuthorizationAdvisorProxyFactory();
-
-	public ReactiveAuthorizationAdvisorProxyFactory() {
-		List<AuthorizationAdvisor> advisors = new ArrayList<>();
-		advisors.add(AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize());
-		advisors.add(AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize());
-		advisors.add(new PreFilterAuthorizationReactiveMethodInterceptor());
-		advisors.add(new PostFilterAuthorizationReactiveMethodInterceptor());
-		advisors.add(new AuthorizeReturnObjectMethodInterceptor(this));
-		this.defaults.setAdvisors(advisors);
-	}
-
-	/**
-	 * Proxy an object to enforce authorization advice.
-	 *
-	 * <p>
-	 * Proxies any instance of a non-final class or a class that implements more than one
-	 * interface.
-	 *
-	 * <p>
-	 * If {@code target} is an {@link Iterator}, {@link Collection}, {@link Array},
-	 * {@link Map}, {@link Stream}, or {@link Optional}, then the element or value type is
-	 * proxied.
-	 *
-	 * <p>
-	 * If {@code target} is a {@link Class}, then {@link ProxyFactory#getProxyClass} is
-	 * invoked instead.
-	 * @param target the instance to proxy
-	 * @return the proxied instance
-	 */
-	@Override
-	public Object proxy(Object target) {
-		if (target instanceof Mono<?> mono) {
-			return proxyMono(mono);
-		}
-		if (target instanceof Flux<?> flux) {
-			return proxyFlux(flux);
-		}
-		return this.defaults.proxy(target);
-	}
-
-	/**
-	 * Add advisors that should be included to each proxy created.
-	 *
-	 * <p>
-	 * All advisors are re-sorted by their advisor order.
-	 * @param advisors the advisors to add
-	 */
-	public void setAdvisors(AuthorizationAdvisor... advisors) {
-		this.defaults.setAdvisors(advisors);
-	}
-
-	/**
-	 * Add advisors that should be included to each proxy created.
-	 *
-	 * <p>
-	 * All advisors are re-sorted by their advisor order.
-	 * @param advisors the advisors to add
-	 */
-	public void setAdvisors(Collection<AuthorizationAdvisor> advisors) {
-		this.defaults.setAdvisors(advisors);
-	}
-
-	private Mono<?> proxyMono(Mono<?> mono) {
-		return mono.map(this::proxy);
-	}
-
-	private Flux<?> proxyFlux(Flux<?> flux) {
-		return flux.map(this::proxy);
-	}
-
-}

+ 40 - 21
core/src/test/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactoryTests.java

@@ -40,12 +40,14 @@ import org.springframework.aop.Pointcut;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.authentication.TestAuthentication;
+import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory.TargetVisitor;
 import org.springframework.security.authorization.method.AuthorizationAdvisor;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
@@ -64,7 +66,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenPreAuthorizeThenHonors() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Flight flight = new Flight();
 		assertThat(flight.getAltitude()).isEqualTo(35000d);
 		Flight secured = proxy(factory, flight);
@@ -75,7 +77,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenPreAuthorizeOnInterfaceThenHonors() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		assertThat(this.alan.getFirstName()).isEqualTo("alan");
 		User secured = proxy(factory, this.alan);
 		assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured::getFirstName);
@@ -89,7 +91,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenPreAuthorizeOnRecordThenHonors() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		HasSecret repo = new Repository("secret");
 		assertThat(repo.secret()).isEqualTo("secret");
 		HasSecret secured = proxy(factory, repo);
@@ -102,7 +104,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenImmutableListThenReturnsSecuredImmutableList() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		List<Flight> flights = List.of(this.flight);
 		List<Flight> secured = proxy(factory, flights);
 		secured.forEach(
@@ -114,7 +116,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenImmutableSetThenReturnsSecuredImmutableSet() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Set<Flight> flights = Set.of(this.flight);
 		Set<Flight> secured = proxy(factory, flights);
 		secured.forEach(
@@ -126,7 +128,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenQueueThenReturnsSecuredQueue() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Queue<Flight> flights = new LinkedList<>(List.of(this.flight));
 		Queue<Flight> secured = proxy(factory, flights);
 		assertThat(flights.size()).isEqualTo(secured.size());
@@ -138,7 +140,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenImmutableSortedSetThenReturnsSecuredImmutableSortedSet() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		SortedSet<User> users = Collections.unmodifiableSortedSet(new TreeSet<>(Set.of(this.alan)));
 		SortedSet<User> secured = proxy(factory, users);
 		secured
@@ -150,7 +152,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenImmutableSortedMapThenReturnsSecuredImmutableSortedMap() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		SortedMap<String, User> users = Collections
 			.unmodifiableSortedMap(new TreeMap<>(Map.of(this.alan.getId(), this.alan)));
 		SortedMap<String, User> secured = proxy(factory, users);
@@ -163,7 +165,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenImmutableMapThenReturnsSecuredImmutableMap() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Map<String, User> users = Map.of(this.alan.getId(), this.alan);
 		Map<String, User> secured = proxy(factory, users);
 		secured.forEach(
@@ -175,7 +177,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenMutableListThenReturnsSecuredMutableList() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		List<Flight> flights = new ArrayList<>(List.of(this.flight));
 		List<Flight> secured = proxy(factory, flights);
 		secured.forEach(
@@ -187,7 +189,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenMutableSetThenReturnsSecuredMutableSet() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Set<Flight> flights = new HashSet<>(Set.of(this.flight));
 		Set<Flight> secured = proxy(factory, flights);
 		secured.forEach(
@@ -199,7 +201,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenMutableSortedSetThenReturnsSecuredMutableSortedSet() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		SortedSet<User> users = new TreeSet<>(Set.of(this.alan));
 		SortedSet<User> secured = proxy(factory, users);
 		secured.forEach((u) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(u::getFirstName));
@@ -210,7 +212,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenMutableSortedMapThenReturnsSecuredMutableSortedMap() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		SortedMap<String, User> users = new TreeMap<>(Map.of(this.alan.getId(), this.alan));
 		SortedMap<String, User> secured = proxy(factory, users);
 		secured.forEach((id, u) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(u::getFirstName));
@@ -221,7 +223,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenMutableMapThenReturnsSecuredMutableMap() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Map<String, User> users = new HashMap<>(Map.of(this.alan.getId(), this.alan));
 		Map<String, User> secured = proxy(factory, users);
 		secured.forEach((id, u) -> assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(u::getFirstName));
@@ -232,7 +234,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenPreAuthorizeForOptionalThenHonors() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Optional<Flight> flights = Optional.of(this.flight);
 		assertThat(flights.get().getAltitude()).isEqualTo(35000d);
 		Optional<Flight> secured = proxy(factory, flights);
@@ -243,7 +245,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenPreAuthorizeForStreamThenHonors() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Stream<Flight> flights = Stream.of(this.flight);
 		Stream<Flight> secured = proxy(factory, flights);
 		assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.forEach(Flight::getAltitude));
@@ -253,7 +255,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenPreAuthorizeForArrayThenHonors() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Flight[] flights = { this.flight };
 		Flight[] secured = proxy(factory, flights);
 		assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(secured[0]::getAltitude);
@@ -263,7 +265,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenPreAuthorizeForIteratorThenHonors() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Iterator<Flight> flights = List.of(this.flight).iterator();
 		Iterator<Flight> secured = proxy(factory, flights);
 		assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.next().getAltitude());
@@ -273,7 +275,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenPreAuthorizeForIterableThenHonors() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Iterable<User> users = new UserRepository();
 		Iterable<User> secured = proxy(factory, users);
 		assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> secured.forEach(User::getFirstName));
@@ -282,7 +284,7 @@ public class AuthorizationAdvisorProxyFactoryTests {
 
 	@Test
 	public void proxyWhenPreAuthorizeForClassThenHonors() {
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		Class<Flight> clazz = proxy(factory, Flight.class);
 		assertThat(clazz.getSimpleName()).contains("SpringCGLIB$$");
 		Flight secured = proxy(factory, this.flight);
@@ -297,13 +299,30 @@ public class AuthorizationAdvisorProxyFactoryTests {
 		AuthorizationAdvisor advisor = mock(AuthorizationAdvisor.class);
 		given(advisor.getAdvice()).willReturn(advisor);
 		given(advisor.getPointcut()).willReturn(Pointcut.TRUE);
-		AuthorizationAdvisorProxyFactory factory = new AuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
 		factory.setAdvisors(advisor);
 		Flight flight = proxy(factory, this.flight);
 		flight.getAltitude();
 		verify(advisor, atLeastOnce()).getPointcut();
 	}
 
+	@Test
+	public void setTargetVisitorThenUses() {
+		TargetVisitor visitor = mock(TargetVisitor.class);
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
+		factory.setTargetVisitor(visitor);
+		factory.proxy(new Flight());
+		verify(visitor).visit(any(), any());
+	}
+
+	@Test
+	public void setTargetVisitorIgnoreValueTypesThenIgnores() {
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
+		assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> ((Integer) factory.proxy(35)).intValue());
+		factory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
+		assertThat(factory.proxy(35)).isEqualTo(35);
+	}
+
 	private Authentication authenticated(String user, String... authorities) {
 		return TestAuthentication.authenticated(TestAuthentication.withUsername(user).authorities(authorities).build());
 	}

+ 6 - 6
core/src/test/java/org/springframework/security/authorization/ReactiveAuthorizationAdvisorProxyFactoryTests.java

@@ -52,7 +52,7 @@ public class ReactiveAuthorizationAdvisorProxyFactoryTests {
 
 	@Test
 	public void proxyWhenPreAuthorizeThenHonors() {
-		ReactiveAuthorizationAdvisorProxyFactory factory = new ReactiveAuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
 		Flight flight = new Flight();
 		StepVerifier
 			.create(flight.getAltitude().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
@@ -67,7 +67,7 @@ public class ReactiveAuthorizationAdvisorProxyFactoryTests {
 	@Test
 	public void proxyWhenPreAuthorizeOnInterfaceThenHonors() {
 		SecurityContextHolder.getContext().setAuthentication(this.user);
-		ReactiveAuthorizationAdvisorProxyFactory factory = new ReactiveAuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
 		StepVerifier
 			.create(this.alan.getFirstName().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
 			.expectNext("alan")
@@ -89,7 +89,7 @@ public class ReactiveAuthorizationAdvisorProxyFactoryTests {
 
 	@Test
 	public void proxyWhenPreAuthorizeOnRecordThenHonors() {
-		ReactiveAuthorizationAdvisorProxyFactory factory = new ReactiveAuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
 		HasSecret repo = new Repository(Mono.just("secret"));
 		StepVerifier.create(repo.secret().contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.user)))
 			.expectNext("secret")
@@ -104,7 +104,7 @@ public class ReactiveAuthorizationAdvisorProxyFactoryTests {
 
 	@Test
 	public void proxyWhenPreAuthorizeOnFluxThenHonors() {
-		ReactiveAuthorizationAdvisorProxyFactory factory = new ReactiveAuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
 		Flux<Flight> flights = Flux.just(this.flight);
 		Flux<Flight> secured = proxy(factory, flights);
 		StepVerifier
@@ -115,7 +115,7 @@ public class ReactiveAuthorizationAdvisorProxyFactoryTests {
 
 	@Test
 	public void proxyWhenPreAuthorizeForClassThenHonors() {
-		ReactiveAuthorizationAdvisorProxyFactory factory = new ReactiveAuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
 		Class<Flight> clazz = proxy(factory, Flight.class);
 		assertThat(clazz.getSimpleName()).contains("SpringCGLIB$$");
 		Flight secured = proxy(factory, this.flight);
@@ -129,7 +129,7 @@ public class ReactiveAuthorizationAdvisorProxyFactoryTests {
 		AuthorizationAdvisor advisor = mock(AuthorizationAdvisor.class);
 		given(advisor.getAdvice()).willReturn(advisor);
 		given(advisor.getPointcut()).willReturn(Pointcut.TRUE);
-		ReactiveAuthorizationAdvisorProxyFactory factory = new ReactiveAuthorizationAdvisorProxyFactory();
+		AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withReactiveDefaults();
 		factory.setAdvisors(advisor);
 		Flight flight = proxy(factory, this.flight);
 		flight.getAltitude();

+ 42 - 9
docs/modules/ROOT/pages/servlet/authorization/method-security.adoc

@@ -1812,12 +1812,40 @@ fun getEmailWhenProxiedThenAuthorizes() {
 ----
 ======
 
-[NOTE]
-====
-`@AuthorizeReturnObject` can be placed at the class level. Note, though, that this means Spring Security will proxy any return object, including ``String``, ``Integer`` and other types.
+=== Using `@AuthorizeReturnObject` at the class level
+
+`@AuthorizeReturnObject` can be placed at the class level. Note, though, that this means Spring Security will attempt to proxy any return object, including ``String``, ``Integer`` and other types.
 This is often not what you want to do.
 
-In most cases, you will want to annotate the individual methods.
+If you want to use `@AuthorizeReturnObject` on a class or interface whose methods return value types, like `int`, `String`, `Double` or collections of those types, then you should also publish the appropriate `AuthorizationAdvisorProxyFactory.TargetVisitor` as follows:
+
+
+[tabs]
+======
+Java::
++
+[source,java,role="primary"]
+----
+@Bean
+static Customizer<AuthorizationAdvisorProxyFactory> skipValueTypes() {
+    return (factory) -> factory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
+}
+----
+
+Kotlin::
++
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun skipValueTypes() = Customizer<AuthorizationAdvisorProxyFactory> {
+    it.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes())
+}
+----
+======
+
+[TIP]
+====
+You can set your own `AuthorizationAdvisorProxyFactory.TargetVisitor` to customize the proxying for any set of types
 ====
 
 === Programmatically Proxying
@@ -1877,22 +1905,27 @@ Java::
 +
 [source,java,role="primary"]
 ----
+import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory.TargetVisitor;
 import static org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize;
-
 // ...
 
-AuthorizationProxyFactory proxyFactory = new AuthorizationProxyFactory(preAuthorize());
+AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
+// and if needing to skip value types
+proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes());
 ----
 
 Kotlin::
 +
 [source,kotlin,role="secondary"]
 ----
+import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory.TargetVisitor;
 import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize
 
 // ...
 
 val proxyFactory: AuthorizationProxyFactory = AuthorizationProxyFactory(preAuthorize())
+// and if needing to skip value types
+proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes())
 ----
 ======
 
@@ -1906,7 +1939,7 @@ Java::
 ----
 @Test
 void getEmailWhenProxiedThenAuthorizes() {
-	AuthorizationProxyFactory proxyFactory = new AuthorizationProxyFactory(preAuthorize());
+	AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
     User user = new User("name", "email");
     assertThat(user.getEmail()).isNotNull();
     User securedUser = proxyFactory.proxy(user);
@@ -1920,7 +1953,7 @@ Kotlin::
 ----
 @Test
 fun getEmailWhenProxiedThenAuthorizes() {
-    val proxyFactory: AuthorizationProxyFactory = AuthorizationProxyFactory(preAuthorize())
+    val proxyFactory: AuthorizationProxyFactory = AuthorizationAdvisorProxyFactory.withDefaults()
     val user: User = User("name", "email")
     assertThat(user.getEmail()).isNotNull()
     val securedUser: User = proxyFactory.proxy(user)
@@ -1948,7 +1981,7 @@ Java::
 ----
 @Test
 void getEmailWhenProxiedThenAuthorizes() {
-	AuthorizationProxyFactory proxyFactory = new AuthorizationProxyFactory(preAuthorize());
+	AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
     List<User> users = List.of(ada, albert, marie);
     List<User> securedUsers = proxyFactory.proxy(users);
 	securedUsers.forEach((securedUser) ->