diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java index 31091be05c8..89401f8380e 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * 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. @@ -45,6 +45,7 @@ import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor; import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager; import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor; +import org.springframework.security.authorization.method.PrePostTemplateDefaults; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolderStrategy; @@ -68,6 +69,7 @@ final class PrePostMethodSecurityConfiguration implements ImportAware { @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor preFilterAuthorizationMethodInterceptor( ObjectProvider defaultsProvider, + ObjectProvider methodSecurityDefaultsProvider, ObjectProvider expressionHandlerProvider, ObjectProvider strategyProvider, ObjectProvider roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration, @@ -75,6 +77,7 @@ static MethodInterceptor preFilterAuthorizationMethodInterceptor( PreFilterAuthorizationMethodInterceptor preFilter = new PreFilterAuthorizationMethodInterceptor(); preFilter.setOrder(preFilter.getOrder() + configuration.interceptorOrderOffset); strategyProvider.ifAvailable(preFilter::setSecurityContextHolderStrategy); + methodSecurityDefaultsProvider.ifAvailable(preFilter::setTemplateDefaults); preFilter.setExpressionHandler(new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, roleHierarchyProvider, context)); return preFilter; @@ -84,12 +87,14 @@ static MethodInterceptor preFilterAuthorizationMethodInterceptor( @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor( ObjectProvider defaultsProvider, + ObjectProvider methodSecurityDefaultsProvider, ObjectProvider expressionHandlerProvider, ObjectProvider strategyProvider, ObjectProvider eventPublisherProvider, ObjectProvider registryProvider, ObjectProvider roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration, ApplicationContext context) { PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager(); + methodSecurityDefaultsProvider.ifAvailable(manager::setTemplateDefaults); manager.setExpressionHandler(new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, roleHierarchyProvider, context)); AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor @@ -104,12 +109,14 @@ static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor( @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor( ObjectProvider defaultsProvider, + ObjectProvider methodSecurityDefaultsProvider, ObjectProvider expressionHandlerProvider, ObjectProvider strategyProvider, ObjectProvider eventPublisherProvider, ObjectProvider registryProvider, ObjectProvider roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration, ApplicationContext context) { PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager(); + methodSecurityDefaultsProvider.ifAvailable(manager::setTemplateDefaults); manager.setExpressionHandler(new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, roleHierarchyProvider, context)); AuthorizationManagerAfterMethodInterceptor postAuthorize = AuthorizationManagerAfterMethodInterceptor @@ -124,6 +131,7 @@ static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor( @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static MethodInterceptor postFilterAuthorizationMethodInterceptor( ObjectProvider defaultsProvider, + ObjectProvider methodSecurityDefaultsProvider, ObjectProvider expressionHandlerProvider, ObjectProvider strategyProvider, ObjectProvider roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration, @@ -131,6 +139,7 @@ static MethodInterceptor postFilterAuthorizationMethodInterceptor( PostFilterAuthorizationMethodInterceptor postFilter = new PostFilterAuthorizationMethodInterceptor(); postFilter.setOrder(postFilter.getOrder() + configuration.interceptorOrderOffset); strategyProvider.ifAvailable(postFilter::setSecurityContextHolderStrategy); + methodSecurityDefaultsProvider.ifAvailable(postFilter::setTemplateDefaults); postFilter.setExpressionHandler(new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, roleHierarchyProvider, context)); return postFilter; diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java index 4f53005f7c8..848934bb257 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * 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. @@ -36,6 +36,7 @@ import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor; import org.springframework.security.authorization.method.PreAuthorizeReactiveAuthorizationManager; import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor; +import org.springframework.security.authorization.method.PrePostTemplateDefaults; import org.springframework.security.config.core.GrantedAuthorityDefaults; /** @@ -50,32 +51,48 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor( - MethodSecurityExpressionHandler expressionHandler) { - return new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler); + MethodSecurityExpressionHandler expressionHandler, + ObjectProvider defaultsObjectProvider) { + PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor( + expressionHandler); + defaultsObjectProvider.ifAvailable(interceptor::setTemplateDefaults); + return interceptor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor( - MethodSecurityExpressionHandler expressionHandler, ObjectProvider registryProvider) { - ReactiveAuthorizationManager authorizationManager = manager( - new PreAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider); + MethodSecurityExpressionHandler expressionHandler, + ObjectProvider defaultsObjectProvider, + ObjectProvider registryProvider) { + PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager( + expressionHandler); + defaultsObjectProvider.ifAvailable(manager::setTemplateDefaults); + ReactiveAuthorizationManager authorizationManager = manager(manager, registryProvider); return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor( - MethodSecurityExpressionHandler expressionHandler) { - return new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler); + MethodSecurityExpressionHandler expressionHandler, + ObjectProvider defaultsObjectProvider) { + PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor( + expressionHandler); + defaultsObjectProvider.ifAvailable(interceptor::setTemplateDefaults); + return interceptor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor( - MethodSecurityExpressionHandler expressionHandler, ObjectProvider registryProvider) { - ReactiveAuthorizationManager authorizationManager = manager( - new PostAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider); + MethodSecurityExpressionHandler expressionHandler, + ObjectProvider defaultsObjectProvider, + ObjectProvider registryProvider) { + PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager( + expressionHandler); + ReactiveAuthorizationManager authorizationManager = manager(manager, registryProvider); + defaultsObjectProvider.ifAvailable(manager::setTemplateDefaults); return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager); } diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java index 77988ed102e..f653d3c5875 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java @@ -17,6 +17,8 @@ package org.springframework.security.config.annotation.method.configuration; import java.io.Serializable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -49,12 +51,17 @@ import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; +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.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.authorization.method.MethodInvocationResult; +import org.springframework.security.authorization.method.PrePostTemplateDefaults; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.test.SpringTestContext; @@ -587,6 +594,74 @@ public void allAnnotationsWhenAdviceAfterAllOffsetThenReturnsFilteredList() { assertThat(filtered).containsExactly("DoNotDrop"); } + @Test + @WithMockUser + public void methodeWhenParameterizedPreAuthorizeMetaAnnotationThenPasses() { + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); + assertThat(service.hasRole("USER")).isTrue(); + } + + @Test + @WithMockUser + public void methodRoleWhenPreAuthorizeMetaAnnotationHardcodedParameterThenPasses() { + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); + assertThat(service.hasUserRole()).isTrue(); + } + + @Test + public void methodWhenParameterizedAnnotationThenFails() { + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(service::placeholdersOnlyResolvedByMetaAnnotations); + } + + @Test + @WithMockUser(authorities = "SCOPE_message:read") + public void methodWhenMultiplePlaceholdersHasAuthorityThenPasses() { + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); + assertThat(service.readMessage()).isEqualTo("message"); + } + + @Test + @WithMockUser(roles = "ADMIN") + public void methodWhenMultiplePlaceholdersHasRoleThenPasses() { + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); + assertThat(service.readMessage()).isEqualTo("message"); + } + + @Test + @WithMockUser + public void methodWhenPostAuthorizeMetaAnnotationThenAuthorizes() { + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); + service.startsWithDave("daveMatthews"); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> service.startsWithDave("jenniferHarper")); + } + + @Test + @WithMockUser + public void methodWhenPreFilterMetaAnnotationThenFilters() { + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); + assertThat(service.parametersContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul")))) + .containsExactly("dave"); + } + + @Test + @WithMockUser + public void methodWhenPostFilterMetaAnnotationThenFilters() { + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); + assertThat(service.resultsContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul")))) + .containsExactly("dave"); + } + private static Consumer disallowBeanOverriding() { return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false); } @@ -890,4 +965,100 @@ Authz authz() { } + @Configuration + @EnableMethodSecurity + static class MetaAnnotationPlaceholderConfig { + + @Bean + PrePostTemplateDefaults methodSecurityDefaults() { + return new PrePostTemplateDefaults(); + } + + @Bean + MetaAnnotationService methodSecurityService() { + return new MetaAnnotationService(); + } + + } + + static class MetaAnnotationService { + + @RequireRole(role = "#role") + boolean hasRole(String role) { + return true; + } + + @RequireRole(role = "'USER'") + boolean hasUserRole() { + return true; + } + + @PreAuthorize("hasRole({role})") + void placeholdersOnlyResolvedByMetaAnnotations() { + } + + @HasClaim(claim = "message:read", roles = { "'ADMIN'" }) + String readMessage() { + return "message"; + } + + @ResultStartsWith("dave") + String startsWithDave(String value) { + return value; + } + + @ParameterContains("dave") + List parametersContainDave(List list) { + return list; + } + + @ResultContains("dave") + List resultsContainDave(List list) { + return list; + } + + } + + @Retention(RetentionPolicy.RUNTIME) + @PreAuthorize("hasRole({role})") + @interface RequireRole { + + String role(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @PreAuthorize("hasAuthority('SCOPE_{claim}') || hasAnyRole({roles})") + @interface HasClaim { + + String claim(); + + String[] roles() default {}; + + } + + @Retention(RetentionPolicy.RUNTIME) + @PostAuthorize("returnObject.startsWith('{value}')") + @interface ResultStartsWith { + + String value(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @PreFilter("filterObject.contains('{value}')") + @interface ParameterContains { + + String value(); + + } + + @Retention(RetentionPolicy.RUNTIME) + @PostFilter("filterObject.contains('{value}')") + @interface ResultContains { + + String value(); + + } + } diff --git a/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java index 23693f7c93d..d1aa34f8e7d 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AbstractExpressionAttributeRegistry.java @@ -16,14 +16,20 @@ package org.springframework.security.authorization.method; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import org.aopalliance.intercept.MethodInvocation; import org.springframework.core.MethodClassKey; import org.springframework.lang.NonNull; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.util.Assert; /** * For internal use only, as this contract is likely to change @@ -35,6 +41,10 @@ abstract class AbstractExpressionAttributeRegistry cachedAttributes = new ConcurrentHashMap<>(); + private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + + private PrePostTemplateDefaults defaults; + /** * Returns an {@link ExpressionAttribute} for the {@link MethodInvocation}. * @param mi the {@link MethodInvocation} to use @@ -58,6 +68,28 @@ final T getAttribute(Method method, Class targetClass) { return this.cachedAttributes.computeIfAbsent(cacheKey, (k) -> resolveAttribute(method, targetClass)); } + final Function findUniqueAnnotation(Class type) { + return (this.defaults != null) ? AuthorizationAnnotationUtils.withDefaults(type, this.defaults) + : AuthorizationAnnotationUtils.withDefaults(type); + } + + /** + * Returns the {@link MethodSecurityExpressionHandler}. + * @return the {@link MethodSecurityExpressionHandler} to use + */ + MethodSecurityExpressionHandler getExpressionHandler() { + return this.expressionHandler; + } + + void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.expressionHandler = expressionHandler; + } + + void setTemplateDefaults(PrePostTemplateDefaults defaults) { + this.defaults = defaults; + } + /** * Subclasses should implement this method to provide the non-null * {@link ExpressionAttribute} for the method and the target class. diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java index 9f37603826c..ffeab6ec9db 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAnnotationUtils.java @@ -19,13 +19,19 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.function.Function; import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.core.annotation.RepeatableContainers; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.util.PropertyPlaceholderHelper; /** * A collection of utility methods that check for, and error on, conflicting annotations. @@ -50,6 +56,43 @@ */ final class AuthorizationAnnotationUtils { + static Function withDefaults(Class type, + PrePostTemplateDefaults defaults) { + Function, A> map = (mergedAnnotation) -> { + if (mergedAnnotation.getMetaSource() == null) { + return mergedAnnotation.synthesize(); + } + PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("{", "}", null, + defaults.isIgnoreUnknown()); + String expression = (String) mergedAnnotation.asMap().get("value"); + Map annotationProperties = mergedAnnotation.getMetaSource().asMap(); + Map stringProperties = new HashMap<>(); + for (Map.Entry property : annotationProperties.entrySet()) { + String key = property.getKey(); + Object value = property.getValue(); + String asString = (value instanceof String) ? (String) value + : DefaultConversionService.getSharedInstance().convert(value, String.class); + stringProperties.put(key, asString); + } + AnnotatedElement annotatedElement = (AnnotatedElement) mergedAnnotation.getSource(); + String value = helper.replacePlaceholders(expression, stringProperties::get); + return MergedAnnotation.of(annotatedElement, type, Collections.singletonMap("value", value)).synthesize(); + }; + return (annotatedElement) -> findDistinctAnnotation(annotatedElement, type, map); + } + + static Function withDefaults(Class type) { + return (annotatedElement) -> findDistinctAnnotation(annotatedElement, type, MergedAnnotation::synthesize); + } + + static A findUniqueAnnotation(Method method, Class annotationType) { + return findDistinctAnnotation(method, annotationType, MergedAnnotation::synthesize); + } + + static A findUniqueAnnotation(Class type, Class annotationType) { + return findDistinctAnnotation(type, annotationType, MergedAnnotation::synthesize); + } + /** * Perform an exhaustive search on the type hierarchy of the given {@link Method} for * the annotation of type {@code annotationType}, including any annotations using @@ -64,8 +107,9 @@ final class AuthorizationAnnotationUtils { * @throws AnnotationConfigurationException if more than one unique instance of the * annotation is found */ - static A findUniqueAnnotation(Method method, Class annotationType) { - return findDistinctAnnotation(method, annotationType); + static A findUniqueAnnotation(Method method, Class annotationType, + Function, A> map) { + return findDistinctAnnotation(method, annotationType, map); } /** @@ -82,18 +126,18 @@ static A findUniqueAnnotation(Method method, Class ann * @throws AnnotationConfigurationException if more than one unique instance of the * annotation is found */ - static A findUniqueAnnotation(Class type, Class annotationType) { - return findDistinctAnnotation(type, annotationType); + static A findUniqueAnnotation(Class type, Class annotationType, + Function, A> map) { + return findDistinctAnnotation(type, annotationType, map); } private static A findDistinctAnnotation(AnnotatedElement annotatedElement, - Class annotationType) { + Class annotationType, Function, A> map) { MergedAnnotations mergedAnnotations = MergedAnnotations.from(annotatedElement, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none()); - List annotations = mergedAnnotations.stream(annotationType) .map(MergedAnnotation::withNonMergedAttributes) - .map(MergedAnnotation::synthesize) + .map(map) .distinct() .toList(); diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java index 0835a45acb3..ee8e26d58a8 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java @@ -46,7 +46,19 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry = new PostAuthorizeExpressionAttributeRegistry(expressionHandler); + this.registry.setExpressionHandler(expressionHandler); + } + + /** + * Configure pre/post-authorization template resolution + *

+ * By default, this value is null, which indicates that templates should + * not be resolved. + * @param defaults - whether to resolve pre/post-authorization templates parameters + * @since 6.3 + */ + public void setTemplateDefaults(PrePostTemplateDefaults defaults) { + this.registry.setTemplateDefaults(defaults); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java index 300fd08f7ee..a7ca347b4aa 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java @@ -16,16 +16,15 @@ package org.springframework.security.authorization.method; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.function.Function; import reactor.util.annotation.NonNull; import org.springframework.aop.support.AopUtils; import org.springframework.expression.Expression; -import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.util.Assert; /** * For internal use only, as this contract is likely to change. @@ -36,21 +35,6 @@ */ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private final MethodSecurityExpressionHandler expressionHandler; - - PostAuthorizeExpressionAttributeRegistry() { - this(new DefaultMethodSecurityExpressionHandler()); - } - - PostAuthorizeExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { - Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.expressionHandler = expressionHandler; - } - - MethodSecurityExpressionHandler getExpressionHandler() { - return this.expressionHandler; - } - @NonNull @Override ExpressionAttribute resolveAttribute(Method method, Class targetClass) { @@ -59,15 +43,14 @@ ExpressionAttribute resolveAttribute(Method method, Class targetClass) { if (postAuthorize == null) { return ExpressionAttribute.NULL_ATTRIBUTE; } - Expression postAuthorizeExpression = this.expressionHandler.getExpressionParser() - .parseExpression(postAuthorize.value()); - return new ExpressionAttribute(postAuthorizeExpression); + Expression expression = getExpressionHandler().getExpressionParser().parseExpression(postAuthorize.value()); + return new ExpressionAttribute(expression); } private PostAuthorize findPostAuthorizeAnnotation(Method method, Class targetClass) { - PostAuthorize postAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostAuthorize.class); - return (postAuthorize != null) ? postAuthorize : AuthorizationAnnotationUtils - .findUniqueAnnotation(targetClass(method, targetClass), PostAuthorize.class); + Function lookup = findUniqueAnnotation(PostAuthorize.class); + PostAuthorize postAuthorize = lookup.apply(method); + return (postAuthorize != null) ? postAuthorize : lookup.apply(targetClass(method, targetClass)); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java index 214fa9654eb..1847ba3c1f5 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java @@ -38,7 +38,7 @@ public final class PostAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager { - private final PostAuthorizeExpressionAttributeRegistry registry; + private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); public PostAuthorizeReactiveAuthorizationManager() { this(new DefaultMethodSecurityExpressionHandler()); @@ -46,7 +46,19 @@ public PostAuthorizeReactiveAuthorizationManager() { public PostAuthorizeReactiveAuthorizationManager(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.registry = new PostAuthorizeExpressionAttributeRegistry(expressionHandler); + this.registry.setExpressionHandler(expressionHandler); + } + + /** + * Configure pre/post-authorization template resolution + *

+ * By default, this value is null, which indicates that templates should + * not be resolved. + * @param defaults - whether to resolve pre/post-authorization templates parameters + * @since 6.3 + */ + public void setTemplateDefaults(PrePostTemplateDefaults defaults) { + this.registry.setTemplateDefaults(defaults); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java index 99630298a23..aadc75c0036 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java @@ -67,7 +67,19 @@ public PostFilterAuthorizationMethodInterceptor() { * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry = new PostFilterExpressionAttributeRegistry(expressionHandler); + this.registry.setExpressionHandler(expressionHandler); + } + + /** + * Configure pre/post-authorization template resolution + *

+ * By default, this value is null, which indicates that templates should + * not be resolved. + * @param defaults - whether to resolve pre/post-authorization templates parameters + * @since 6.3 + */ + public void setTemplateDefaults(PrePostTemplateDefaults defaults) { + this.registry.setTemplateDefaults(defaults); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java index c4eea895544..19dbbb7e0be 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java @@ -49,7 +49,7 @@ public final class PostFilterAuthorizationReactiveMethodInterceptor implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { - private final PostFilterExpressionAttributeRegistry registry; + private final PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry(); private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class); @@ -67,7 +67,19 @@ public PostFilterAuthorizationReactiveMethodInterceptor() { */ public PostFilterAuthorizationReactiveMethodInterceptor(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.registry = new PostFilterExpressionAttributeRegistry(expressionHandler); + this.registry.setExpressionHandler(expressionHandler); + } + + /** + * Configure pre/post-authorization template resolution + *

+ * By default, this value is null, which indicates that templates should + * not be resolved. + * @param defaults - whether to resolve pre/post-authorization templates parameters + * @since 6.3 + */ + public void setTemplateDefaults(PrePostTemplateDefaults defaults) { + this.registry.setTemplateDefaults(defaults); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java index 541d2caae54..8b80c5101c6 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java @@ -16,15 +16,14 @@ package org.springframework.security.authorization.method; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.function.Function; import org.springframework.aop.support.AopUtils; import org.springframework.expression.Expression; import org.springframework.lang.NonNull; -import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PostFilter; -import org.springframework.util.Assert; /** * For internal use only, as this contract is likely to change. @@ -35,21 +34,6 @@ */ final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private final MethodSecurityExpressionHandler expressionHandler; - - PostFilterExpressionAttributeRegistry() { - this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); - } - - PostFilterExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { - Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.expressionHandler = expressionHandler; - } - - MethodSecurityExpressionHandler getExpressionHandler() { - return this.expressionHandler; - } - @NonNull @Override ExpressionAttribute resolveAttribute(Method method, Class targetClass) { @@ -58,15 +42,15 @@ ExpressionAttribute resolveAttribute(Method method, Class targetClass) { if (postFilter == null) { return ExpressionAttribute.NULL_ATTRIBUTE; } - Expression postFilterExpression = this.expressionHandler.getExpressionParser() + Expression postFilterExpression = getExpressionHandler().getExpressionParser() .parseExpression(postFilter.value()); return new ExpressionAttribute(postFilterExpression); } private PostFilter findPostFilterAnnotation(Method method, Class targetClass) { - PostFilter postFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostFilter.class); - return (postFilter != null) ? postFilter - : AuthorizationAnnotationUtils.findUniqueAnnotation(targetClass(method, targetClass), PostFilter.class); + Function lookup = findUniqueAnnotation(PostFilter.class); + PostFilter postFilter = lookup.apply(method); + return (postFilter != null) ? postFilter : lookup.apply(targetClass(method, targetClass)); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java index b58a3a7b7ef..c1f7a6b9e02 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java @@ -46,7 +46,19 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry = new PreAuthorizeExpressionAttributeRegistry(expressionHandler); + this.registry.setExpressionHandler(expressionHandler); + } + + /** + * Configure pre/post-authorization template resolution + *

+ * By default, this value is null, which indicates that templates should + * not be resolved. + * @param defaults - whether to resolve pre/post-authorization templates parameters + * @since 6.3 + */ + public void setTemplateDefaults(PrePostTemplateDefaults defaults) { + this.registry.setTemplateDefaults(defaults); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java index 0c445a37d1f..38994412980 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java @@ -16,16 +16,15 @@ package org.springframework.security.authorization.method; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.function.Function; import reactor.util.annotation.NonNull; import org.springframework.aop.support.AopUtils; import org.springframework.expression.Expression; -import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.util.Assert; /** * For internal use only, as this contract is likely to change. @@ -36,25 +35,6 @@ */ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private final MethodSecurityExpressionHandler expressionHandler; - - PreAuthorizeExpressionAttributeRegistry() { - this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); - } - - PreAuthorizeExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { - Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.expressionHandler = expressionHandler; - } - - /** - * Returns the {@link MethodSecurityExpressionHandler}. - * @return the {@link MethodSecurityExpressionHandler} to use - */ - MethodSecurityExpressionHandler getExpressionHandler() { - return this.expressionHandler; - } - @NonNull @Override ExpressionAttribute resolveAttribute(Method method, Class targetClass) { @@ -63,15 +43,14 @@ ExpressionAttribute resolveAttribute(Method method, Class targetClass) { if (preAuthorize == null) { return ExpressionAttribute.NULL_ATTRIBUTE; } - Expression preAuthorizeExpression = this.expressionHandler.getExpressionParser() - .parseExpression(preAuthorize.value()); - return new ExpressionAttribute(preAuthorizeExpression); + Expression expression = getExpressionHandler().getExpressionParser().parseExpression(preAuthorize.value()); + return new ExpressionAttribute(expression); } private PreAuthorize findPreAuthorizeAnnotation(Method method, Class targetClass) { - PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class); - return (preAuthorize != null) ? preAuthorize : AuthorizationAnnotationUtils - .findUniqueAnnotation(targetClass(method, targetClass), PreAuthorize.class); + Function lookup = findUniqueAnnotation(PreAuthorize.class); + PreAuthorize preAuthorize = lookup.apply(method); + return (preAuthorize != null) ? preAuthorize : lookup.apply(targetClass(method, targetClass)); } } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java index cffdf832354..383a0201130 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java @@ -37,7 +37,7 @@ */ public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager { - private final PreAuthorizeExpressionAttributeRegistry registry; + private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry(); public PreAuthorizeReactiveAuthorizationManager() { this(new DefaultMethodSecurityExpressionHandler()); @@ -45,7 +45,19 @@ public PreAuthorizeReactiveAuthorizationManager() { public PreAuthorizeReactiveAuthorizationManager(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.registry = new PreAuthorizeExpressionAttributeRegistry(expressionHandler); + this.registry.setExpressionHandler(expressionHandler); + } + + /** + * Configure pre/post-authorization template resolution + *

+ * By default, this value is null, which indicates that templates should + * not be resolved. + * @param defaults - whether to resolve pre/post-authorization templates parameters + * @since 6.3 + */ + public void setTemplateDefaults(PrePostTemplateDefaults defaults) { + this.registry.setTemplateDefaults(defaults); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java index bde5f1ee9d2..39ae4e257ca 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java @@ -68,7 +68,19 @@ public PreFilterAuthorizationMethodInterceptor() { * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry = new PreFilterExpressionAttributeRegistry(expressionHandler); + this.registry.setExpressionHandler(expressionHandler); + } + + /** + * Configure pre/post-authorization template resolution + *

+ * By default, this value is null, which indicates that templates should + * not be resolved. + * @param defaults - whether to resolve pre/post-authorization templates parameters + * @since 6.3 + */ + public void setTemplateDefaults(PrePostTemplateDefaults defaults) { + this.registry.setTemplateDefaults(defaults); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java index 04c5d4b337e..18f7d61123b 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java @@ -53,7 +53,7 @@ public final class PreFilterAuthorizationReactiveMethodInterceptor implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { - private final PreFilterExpressionAttributeRegistry registry; + private final PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry(); private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class); @@ -70,7 +70,19 @@ public PreFilterAuthorizationReactiveMethodInterceptor() { */ public PreFilterAuthorizationReactiveMethodInterceptor(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.registry = new PreFilterExpressionAttributeRegistry(expressionHandler); + this.registry.setExpressionHandler(expressionHandler); + } + + /** + * Configure pre/post-authorization template resolution + *

+ * By default, this value is null, which indicates that templates should + * not be resolved. + * @param defaults - whether to resolve pre/post-authorization templates parameters + * @since 6.3 + */ + public void setTemplateDefaults(PrePostTemplateDefaults defaults) { + this.registry.setTemplateDefaults(defaults); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java index 67bab2c7ff7..95425c438dd 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java @@ -16,15 +16,14 @@ package org.springframework.security.authorization.method; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.function.Function; import org.springframework.aop.support.AopUtils; import org.springframework.expression.Expression; import org.springframework.lang.NonNull; -import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PreFilter; -import org.springframework.util.Assert; /** * For internal use only, as this contract is likely to change. @@ -36,21 +35,6 @@ final class PreFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private final MethodSecurityExpressionHandler expressionHandler; - - PreFilterExpressionAttributeRegistry() { - this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); - } - - PreFilterExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { - Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.expressionHandler = expressionHandler; - } - - MethodSecurityExpressionHandler getExpressionHandler() { - return this.expressionHandler; - } - @NonNull @Override PreFilterExpressionAttribute resolveAttribute(Method method, Class targetClass) { @@ -59,15 +43,15 @@ PreFilterExpressionAttribute resolveAttribute(Method method, Class targetClas if (preFilter == null) { return PreFilterExpressionAttribute.NULL_ATTRIBUTE; } - Expression preFilterExpression = this.expressionHandler.getExpressionParser() + Expression preFilterExpression = getExpressionHandler().getExpressionParser() .parseExpression(preFilter.value()); return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget()); } private PreFilter findPreFilterAnnotation(Method method, Class targetClass) { - PreFilter preFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreFilter.class); - return (preFilter != null) ? preFilter - : AuthorizationAnnotationUtils.findUniqueAnnotation(targetClass(method, targetClass), PreFilter.class); + Function lookup = findUniqueAnnotation(PreFilter.class); + PreFilter preFilter = lookup.apply(method); + return (preFilter != null) ? preFilter : lookup.apply(targetClass(method, targetClass)); } static final class PreFilterExpressionAttribute extends ExpressionAttribute { diff --git a/core/src/main/java/org/springframework/security/authorization/method/PrePostTemplateDefaults.java b/core/src/main/java/org/springframework/security/authorization/method/PrePostTemplateDefaults.java new file mode 100644 index 00000000000..7c309582c02 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authorization/method/PrePostTemplateDefaults.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authorization.method; + +/** + * A component for configuring various cross-cutting aspects of pre/post method security + * + * @author Josh Cummings + * @since 6.3 + * @see org.springframework.security.access.prepost.PreAuthorize + * @see org.springframework.security.access.prepost.PostAuthorize + * @see org.springframework.security.access.prepost.PreFilter + * @see org.springframework.security.access.prepost.PostFilter + */ +public final class PrePostTemplateDefaults { + + private boolean ignoreUnknown = true; + + /** + * Whether template resolution should ignore placeholders it doesn't recognize. + *

+ * By default, this value is true. + * @since 6.3 + */ + public boolean isIgnoreUnknown() { + return this.ignoreUnknown; + } + + /** + * Configure template resolution to ignore unknown placeholders. When set to + * false, template resolution will throw an exception for unknown + * placeholders. + *

+ * By default, this value is true. + * @param ignoreUnknown - whether to ignore unknown placeholders parameters + * @since 6.3 + */ + public void setIgnoreUnknown(boolean ignoreUnknown) { + this.ignoreUnknown = ignoreUnknown; + } + +} diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index 4d3f55122fc..ccd328e7d5b 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -900,6 +900,157 @@ open class BankService { This results in more readable method definitions. +==== Templating Meta-Annotation Expressions + +You can also opt into using meta-annotation templates, which allow for much more powerful annotation definitions. + +First, publish the following bean: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +static PrePostTemplateDefaults prePostTemplateDefaults() { + return new PrePostTemplateDefaults(); +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +companion object { + @Bean + fun prePostTemplateDefaults(): PrePostTemplateDefaults { + return PrePostTemplateDefaults() + } +} +---- +====== + +Now instead of `@IsAdmin`, you can create something more powerful like `@HasRole` like so: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('{value}')") +public @interface HasRole { + String value(); +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Target(ElementType.METHOD, ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasRole('{value}')") +annotation class IsAdmin(val value: String) +---- +====== + +And the result is that on your secured methods you can now do the following instead: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Component +public class BankService { + @HasRole("ADMIN") + public Account readAccount(Long id) { + // ... is only returned if the `Account` belongs to the logged in user + } +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Component +open class BankService { + @HasRole("ADMIN") + fun readAccount(val id: Long): Account { + // ... is only returned if the `Account` belongs to the logged in user + } +} +---- +====== + +Note that this works with method variables and all annotation types, too, though you will want to be careful to correctly take care of quotation marks so the resulting SpEL expression is correct. + +For example, consider the following `@HasAnyRole` annotation: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAnyRole({roles})") +public @interface HasAnyRole { + String[] roles(); +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Target(ElementType.METHOD, ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@PreAuthorize("hasAnyRole({roles})") +annotation class HasAnyRole(val roles: Array) +---- +====== + +In that case, you'll notice that you should not use the quotation marks in the expression, but instead in the parameter value like so: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Component +public class BankService { + @HasAnyRole(roles = { "'USER'", "'ADMIN'" }) + public Account readAccount(Long id) { + // ... is only returned if the `Account` belongs to the logged in user + } +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Component +open class BankService { + @HasAnyRole(roles = arrayOf("'USER'", "'ADMIN'")) + fun readAccount(val id: Long): Account { + // ... is only returned if the `Account` belongs to the logged in user + } +} +---- +====== + +so that, once replaced, the expression becomes `@PreAuthorize("hasAnyRole('USER', 'ADMIN')")`. + [[enable-annotation]] === Enabling Certain Annotations