Add Value-Type Ignore Support

Issue gh-14597
This commit is contained in:
Josh Cummings 2024-03-19 18:45:51 -06:00
parent b60e037005
commit 795e44d11f
9 changed files with 464 additions and 336 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -34,17 +34,28 @@ import java.util.SortedMap;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.aop.Advisor; import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.framework.ProxyFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.NonNull;
import org.springframework.security.authorization.method.AuthorizationAdvisor; import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor; 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.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.AuthorizeReturnObjectMethodInterceptor;
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor; 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.PreFilterAuthorizationMethodInterceptor;
import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
@ -63,8 +74,7 @@ import org.springframework.util.ClassUtils;
* like so: * like so:
* *
* <pre> * <pre>
* AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor.preAuthorize(); * AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
* AuthorizationProxyFactory proxyFactory = new AuthorizationProxyFactory(preAuthorize);
* Foo foo = new Foo(); * Foo foo = new Foo();
* foo.bar(); // passes * foo.bar(); // passes
* Foo securedFoo = proxyFactory.proxy(foo); * Foo securedFoo = proxyFactory.proxy(foo);
@ -74,18 +84,56 @@ import org.springframework.util.ClassUtils;
* @author Josh Cummings * @author Josh Cummings
* @since 6.3 * @since 6.3
*/ */
public final class AuthorizationAdvisorProxyFactory implements AuthorizationProxyFactory { public final class AuthorizationAdvisorProxyFactory
implements AuthorizationProxyFactory, Iterable<AuthorizationAdvisor> {
private List<AuthorizationAdvisor> advisors = new ArrayList<>(); private static final boolean isReactivePresent = ClassUtils.isPresent("reactor.core.publisher.Mono", null);
public AuthorizationAdvisorProxyFactory() { 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;
private TargetVisitor visitor = DEFAULT_VISITOR;
private AuthorizationAdvisorProxyFactory(List<AuthorizationAdvisor> advisors) {
this.advisors = new ArrayList<>(advisors);
this.advisors.add(new AuthorizeReturnObjectMethodInterceptor(this));
setAdvisors(this.advisors);
}
/**
* 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<>(); List<AuthorizationAdvisor> advisors = new ArrayList<>();
advisors.add(AuthorizationManagerBeforeMethodInterceptor.preAuthorize()); advisors.add(AuthorizationManagerBeforeMethodInterceptor.preAuthorize());
advisors.add(AuthorizationManagerAfterMethodInterceptor.postAuthorize()); advisors.add(AuthorizationManagerAfterMethodInterceptor.postAuthorize());
advisors.add(new PreFilterAuthorizationMethodInterceptor()); advisors.add(new PreFilterAuthorizationMethodInterceptor());
advisors.add(new PostFilterAuthorizationMethodInterceptor()); advisors.add(new PostFilterAuthorizationMethodInterceptor());
advisors.add(new AuthorizeReturnObjectMethodInterceptor(this)); return new AuthorizationAdvisorProxyFactory(advisors);
setAdvisors(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) { if (target == null) {
return null; return null;
} }
if (target instanceof Class<?> targetClass) { Object proxied = this.visitor.visit(this, target);
return proxyClass(targetClass); if (proxied != null) {
} return proxied;
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);
} }
ProxyFactory factory = new ProxyFactory(target); ProxyFactory factory = new ProxyFactory(target);
for (Advisor advisor : this.advisors) { for (Advisor advisor : this.advisors) {
@ -179,27 +195,175 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
AnnotationAwareOrderComparator.sort(this.advisors); AnnotationAwareOrderComparator.sort(this.advisors);
} }
@SuppressWarnings("unchecked") /**
private <T> T proxyCast(T target) { * Use this visitor to navigate the proxy target's hierarchy.
return (T) proxy(target); *
* <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) { @Override
@NonNull
public Iterator<AuthorizationAdvisor> iterator() {
return this.advisors.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 static final class IgnoreValueTypeVisitor implements TargetVisitor {
@Override
public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object object) {
if (ClassUtils.isSimpleValueType(object.getClass())) {
return object;
}
return null;
}
}
private static final class ClassVisitor implements TargetVisitor {
@Override
public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object object) {
if (object instanceof Class<?> targetClass) {
ProxyFactory factory = new ProxyFactory(); ProxyFactory factory = new ProxyFactory();
factory.setTargetClass(targetClass); factory.setTargetClass(targetClass);
factory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass)); factory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass));
factory.setProxyTargetClass(!Modifier.isFinal(targetClass.getModifiers())); factory.setProxyTargetClass(!Modifier.isFinal(targetClass.getModifiers()));
for (Advisor advisor : this.advisors) { for (Advisor advisor : proxyFactory) {
factory.addAdvisors(advisor); factory.addAdvisors(advisor);
} }
return factory.getProxyClass(getClass().getClassLoader()); return factory.getProxyClass(getClass().getClassLoader());
} }
return null;
private <T> Iterable<T> proxyIterable(Iterable<T> iterable) {
return () -> proxyIterator(iterable.iterator());
} }
private <T> Iterator<T> proxyIterator(Iterator<T> iterator) { }
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;
}
@SuppressWarnings("unchecked")
private <T> T proxyCast(AuthorizationProxyFactory proxyFactory, T target) {
return (T) proxyFactory.proxy(target);
}
private <T> Iterable<T> proxyIterable(AuthorizationProxyFactory proxyFactory, Iterable<T> iterable) {
return () -> proxyIterator(proxyFactory, iterable.iterator());
}
private <T> Iterator<T> proxyIterator(AuthorizationProxyFactory proxyFactory, Iterator<T> iterator) {
return new Iterator<>() { return new Iterator<>() {
@Override @Override
public boolean hasNext() { public boolean hasNext() {
@ -208,15 +372,15 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
@Override @Override
public T next() { public T next() {
return proxyCast(iterator.next()); return proxyCast(proxyFactory, iterator.next());
} }
}; };
} }
private <T> SortedSet<T> proxySortedSet(SortedSet<T> set) { private <T> SortedSet<T> proxySortedSet(AuthorizationProxyFactory proxyFactory, SortedSet<T> set) {
SortedSet<T> proxies = new TreeSet<>(set.comparator()); SortedSet<T> proxies = new TreeSet<>(set.comparator());
for (T toProxy : set) { for (T toProxy : set) {
proxies.add(proxyCast(toProxy)); proxies.add(proxyCast(proxyFactory, toProxy));
} }
try { try {
set.clear(); set.clear();
@ -228,10 +392,10 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
} }
} }
private <T> Set<T> proxySet(Set<T> set) { private <T> Set<T> proxySet(AuthorizationProxyFactory proxyFactory, Set<T> set) {
Set<T> proxies = new LinkedHashSet<>(set.size()); Set<T> proxies = new LinkedHashSet<>(set.size());
for (T toProxy : set) { for (T toProxy : set) {
proxies.add(proxyCast(toProxy)); proxies.add(proxyCast(proxyFactory, toProxy));
} }
try { try {
set.clear(); set.clear();
@ -243,20 +407,20 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
} }
} }
private <T> Queue<T> proxyQueue(Queue<T> queue) { private <T> Queue<T> proxyQueue(AuthorizationProxyFactory proxyFactory, Queue<T> queue) {
Queue<T> proxies = new LinkedList<>(); Queue<T> proxies = new LinkedList<>();
for (T toProxy : queue) { for (T toProxy : queue) {
proxies.add(proxyCast(toProxy)); proxies.add(proxyCast(proxyFactory, toProxy));
} }
queue.clear(); queue.clear();
queue.addAll(proxies); queue.addAll(proxies);
return proxies; return proxies;
} }
private <T> List<T> proxyList(List<T> list) { private <T> List<T> proxyList(AuthorizationProxyFactory proxyFactory, List<T> list) {
List<T> proxies = new ArrayList<>(list.size()); List<T> proxies = new ArrayList<>(list.size());
for (T toProxy : list) { for (T toProxy : list) {
proxies.add(proxyCast(toProxy)); proxies.add(proxyCast(proxyFactory, toProxy));
} }
try { try {
list.clear(); list.clear();
@ -268,10 +432,10 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
} }
} }
private Object[] proxyArray(Object[] objects) { private Object[] proxyArray(AuthorizationProxyFactory proxyFactory, Object[] objects) {
List<Object> retain = new ArrayList<>(objects.length); List<Object> retain = new ArrayList<>(objects.length);
for (Object object : objects) { for (Object object : objects) {
retain.add(proxy(object)); retain.add(proxyFactory.proxy(object));
} }
Object[] proxies = (Object[]) Array.newInstance(objects.getClass().getComponentType(), retain.size()); Object[] proxies = (Object[]) Array.newInstance(objects.getClass().getComponentType(), retain.size());
for (int i = 0; i < retain.size(); i++) { for (int i = 0; i < retain.size(); i++) {
@ -280,10 +444,10 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
return proxies; return proxies;
} }
private <K, V> SortedMap<K, V> proxySortedMap(SortedMap<K, V> entries) { private <K, V> SortedMap<K, V> proxySortedMap(AuthorizationProxyFactory proxyFactory, SortedMap<K, V> entries) {
SortedMap<K, V> proxies = new TreeMap<>(entries.comparator()); SortedMap<K, V> proxies = new TreeMap<>(entries.comparator());
for (Map.Entry<K, V> entry : entries.entrySet()) { for (Map.Entry<K, V> entry : entries.entrySet()) {
proxies.put(entry.getKey(), proxyCast(entry.getValue())); proxies.put(entry.getKey(), proxyCast(proxyFactory, entry.getValue()));
} }
try { try {
entries.clear(); entries.clear();
@ -295,10 +459,10 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
} }
} }
private <K, V> Map<K, V> proxyMap(Map<K, V> entries) { private <K, V> Map<K, V> proxyMap(AuthorizationProxyFactory proxyFactory, Map<K, V> entries) {
Map<K, V> proxies = new LinkedHashMap<>(entries.size()); Map<K, V> proxies = new LinkedHashMap<>(entries.size());
for (Map.Entry<K, V> entry : entries.entrySet()) { for (Map.Entry<K, V> entry : entries.entrySet()) {
proxies.put(entry.getKey(), proxyCast(entry.getValue())); proxies.put(entry.getKey(), proxyCast(proxyFactory, entry.getValue()));
} }
try { try {
entries.clear(); entries.clear();
@ -310,13 +474,39 @@ public final class AuthorizationAdvisorProxyFactory implements AuthorizationProx
} }
} }
private Stream<?> proxyStream(Stream<?> stream) { private Stream<?> proxyStream(AuthorizationProxyFactory proxyFactory, Stream<?> stream) {
return stream.map(this::proxy).onClose(stream::close); return stream.map(proxyFactory::proxy).onClose(stream::close);
} }
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private Optional<?> proxyOptional(Optional<?> optional) { private Optional<?> proxyOptional(AuthorizationProxyFactory proxyFactory, Optional<?> optional) {
return optional.map(this::proxy); return optional.map(proxyFactory::proxy);
}
}
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;
}
private Mono<?> proxyMono(AuthorizationProxyFactory proxyFactory, Mono<?> mono) {
return mono.map(proxyFactory::proxy);
}
private Flux<?> proxyFlux(AuthorizationProxyFactory proxyFactory, Flux<?> flux) {
return flux.map(proxyFactory::proxy);
}
} }
} }

View File

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

View File

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

View File

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

View File

@ -1812,12 +1812,40 @@ fun getEmailWhenProxiedThenAuthorizes() {
---- ----
====== ======
[NOTE] === Using `@AuthorizeReturnObject` at the class level
====
`@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. `@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. 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 === Programmatically Proxying
@ -1877,22 +1905,27 @@ Java::
+ +
[source,java,role="primary"] [source,java,role="primary"]
---- ----
import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory.TargetVisitor;
import static org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize; 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:: Kotlin::
+ +
[source,kotlin,role="secondary"] [source,kotlin,role="secondary"]
---- ----
import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory.TargetVisitor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.preAuthorize
// ... // ...
val proxyFactory: AuthorizationProxyFactory = AuthorizationProxyFactory(preAuthorize()) val proxyFactory: AuthorizationProxyFactory = AuthorizationProxyFactory(preAuthorize())
// and if needing to skip value types
proxyFactory.setTargetVisitor(TargetVisitor.defaultsSkipValueTypes())
---- ----
====== ======
@ -1906,7 +1939,7 @@ Java::
---- ----
@Test @Test
void getEmailWhenProxiedThenAuthorizes() { void getEmailWhenProxiedThenAuthorizes() {
AuthorizationProxyFactory proxyFactory = new AuthorizationProxyFactory(preAuthorize()); AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
User user = new User("name", "email"); User user = new User("name", "email");
assertThat(user.getEmail()).isNotNull(); assertThat(user.getEmail()).isNotNull();
User securedUser = proxyFactory.proxy(user); User securedUser = proxyFactory.proxy(user);
@ -1920,7 +1953,7 @@ Kotlin::
---- ----
@Test @Test
fun getEmailWhenProxiedThenAuthorizes() { fun getEmailWhenProxiedThenAuthorizes() {
val proxyFactory: AuthorizationProxyFactory = AuthorizationProxyFactory(preAuthorize()) val proxyFactory: AuthorizationProxyFactory = AuthorizationAdvisorProxyFactory.withDefaults()
val user: User = User("name", "email") val user: User = User("name", "email")
assertThat(user.getEmail()).isNotNull() assertThat(user.getEmail()).isNotNull()
val securedUser: User = proxyFactory.proxy(user) val securedUser: User = proxyFactory.proxy(user)
@ -1948,7 +1981,7 @@ Java::
---- ----
@Test @Test
void getEmailWhenProxiedThenAuthorizes() { void getEmailWhenProxiedThenAuthorizes() {
AuthorizationProxyFactory proxyFactory = new AuthorizationProxyFactory(preAuthorize()); AuthorizationProxyFactory proxyFactory = AuthorizationAdvisorProxyFactory.withDefaults();
List<User> users = List.of(ada, albert, marie); List<User> users = List.of(ada, albert, marie);
List<User> securedUsers = proxyFactory.proxy(users); List<User> securedUsers = proxyFactory.proxy(users);
securedUsers.forEach((securedUser) -> securedUsers.forEach((securedUser) ->