mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 01:02:14 +00:00
Add meta-annotation parameter support
Closes gh-14480
This commit is contained in:
parent
347eeb17d5
commit
4d383023cb
@ -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.PostAuthorizeAuthorizat
|
||||
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<GrantedAuthorityDefaults> defaultsProvider,
|
||||
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
|
||||
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
|
||||
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
|
||||
ObjectProvider<RoleHierarchy> roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration,
|
||||
@ -75,6 +77,7 @@ final class PrePostMethodSecurityConfiguration implements ImportAware {
|
||||
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 @@ final class PrePostMethodSecurityConfiguration implements ImportAware {
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor(
|
||||
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
|
||||
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
|
||||
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
|
||||
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
|
||||
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
|
||||
ObjectProvider<ObservationRegistry> registryProvider, ObjectProvider<RoleHierarchy> 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 @@ final class PrePostMethodSecurityConfiguration implements ImportAware {
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor(
|
||||
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
|
||||
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
|
||||
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
|
||||
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
|
||||
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
|
||||
ObjectProvider<ObservationRegistry> registryProvider, ObjectProvider<RoleHierarchy> 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 @@ final class PrePostMethodSecurityConfiguration implements ImportAware {
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
static MethodInterceptor postFilterAuthorizationMethodInterceptor(
|
||||
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
|
||||
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
|
||||
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
|
||||
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
|
||||
ObjectProvider<RoleHierarchy> roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration,
|
||||
@ -131,6 +139,7 @@ final class PrePostMethodSecurityConfiguration implements ImportAware {
|
||||
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;
|
||||
|
@ -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.PostAuthorizeReactiveAu
|
||||
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<PrePostTemplateDefaults> defaultsObjectProvider) {
|
||||
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor(
|
||||
expressionHandler);
|
||||
defaultsObjectProvider.ifAvailable(interceptor::setTemplateDefaults);
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor(
|
||||
MethodSecurityExpressionHandler expressionHandler, ObjectProvider<ObservationRegistry> registryProvider) {
|
||||
ReactiveAuthorizationManager<MethodInvocation> authorizationManager = manager(
|
||||
new PreAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider);
|
||||
MethodSecurityExpressionHandler expressionHandler,
|
||||
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider,
|
||||
ObjectProvider<ObservationRegistry> registryProvider) {
|
||||
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager(
|
||||
expressionHandler);
|
||||
defaultsObjectProvider.ifAvailable(manager::setTemplateDefaults);
|
||||
ReactiveAuthorizationManager<MethodInvocation> 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<PrePostTemplateDefaults> defaultsObjectProvider) {
|
||||
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor(
|
||||
expressionHandler);
|
||||
defaultsObjectProvider.ifAvailable(interceptor::setTemplateDefaults);
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor(
|
||||
MethodSecurityExpressionHandler expressionHandler, ObjectProvider<ObservationRegistry> registryProvider) {
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager = manager(
|
||||
new PostAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider);
|
||||
MethodSecurityExpressionHandler expressionHandler,
|
||||
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider,
|
||||
ObjectProvider<ObservationRegistry> registryProvider) {
|
||||
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager(
|
||||
expressionHandler);
|
||||
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager = manager(manager, registryProvider);
|
||||
defaultsObjectProvider.ifAvailable(manager::setTemplateDefaults);
|
||||
return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager);
|
||||
}
|
||||
|
||||
|
@ -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.DefaultMethodSecuri
|
||||
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 class PrePostMethodSecurityConfigurationTests {
|
||||
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<ConfigurableWebApplicationContext> disallowBeanOverriding() {
|
||||
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
|
||||
}
|
||||
@ -890,4 +965,100 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@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<String> parametersContainDave(List<String> list) {
|
||||
return list;
|
||||
}
|
||||
|
||||
@ResultContains("dave")
|
||||
List<String> resultsContainDave(List<String> 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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<T extends ExpressionAttribute
|
||||
|
||||
private final Map<MethodClassKey, T> 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 @@ abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute
|
||||
return this.cachedAttributes.computeIfAbsent(cacheKey, (k) -> resolveAttribute(method, targetClass));
|
||||
}
|
||||
|
||||
final <A extends Annotation> Function<AnnotatedElement, A> findUniqueAnnotation(Class<A> 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.
|
||||
|
@ -19,13 +19,19 @@ package org.springframework.security.authorization.method;
|
||||
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 @@ import org.springframework.core.annotation.RepeatableContainers;
|
||||
*/
|
||||
final class AuthorizationAnnotationUtils {
|
||||
|
||||
static <A extends Annotation> Function<AnnotatedElement, A> withDefaults(Class<A> type,
|
||||
PrePostTemplateDefaults defaults) {
|
||||
Function<MergedAnnotation<A>, 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<String, Object> annotationProperties = mergedAnnotation.getMetaSource().asMap();
|
||||
Map<String, String> stringProperties = new HashMap<>();
|
||||
for (Map.Entry<String, Object> 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 <A extends Annotation> Function<AnnotatedElement, A> withDefaults(Class<A> type) {
|
||||
return (annotatedElement) -> findDistinctAnnotation(annotatedElement, type, MergedAnnotation::synthesize);
|
||||
}
|
||||
|
||||
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType) {
|
||||
return findDistinctAnnotation(method, annotationType, MergedAnnotation::synthesize);
|
||||
}
|
||||
|
||||
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> 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 extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType) {
|
||||
return findDistinctAnnotation(method, annotationType);
|
||||
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType,
|
||||
Function<MergedAnnotation<A>, A> map) {
|
||||
return findDistinctAnnotation(method, annotationType, map);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,18 +126,18 @@ final class AuthorizationAnnotationUtils {
|
||||
* @throws AnnotationConfigurationException if more than one unique instance of the
|
||||
* annotation is found
|
||||
*/
|
||||
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType) {
|
||||
return findDistinctAnnotation(type, annotationType);
|
||||
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType,
|
||||
Function<MergedAnnotation<A>, A> map) {
|
||||
return findDistinctAnnotation(type, annotationType, map);
|
||||
}
|
||||
|
||||
private static <A extends Annotation> A findDistinctAnnotation(AnnotatedElement annotatedElement,
|
||||
Class<A> annotationType) {
|
||||
Class<A> annotationType, Function<MergedAnnotation<A>, A> map) {
|
||||
MergedAnnotations mergedAnnotations = MergedAnnotations.from(annotatedElement, SearchStrategy.TYPE_HIERARCHY,
|
||||
RepeatableContainers.none());
|
||||
|
||||
List<A> annotations = mergedAnnotations.stream(annotationType)
|
||||
.map(MergedAnnotation::withNonMergedAttributes)
|
||||
.map(MergedAnnotation::synthesize)
|
||||
.map(map)
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
|
@ -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
|
||||
* <p>
|
||||
* By default, this value is <code>null</code>, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
|
||||
|
||||
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 @@ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionA
|
||||
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<AnnotatedElement, PostAuthorize> lookup = findUniqueAnnotation(PostAuthorize.class);
|
||||
PostAuthorize postAuthorize = lookup.apply(method);
|
||||
return (postAuthorize != null) ? postAuthorize : lookup.apply(targetClass(method, targetClass));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ import org.springframework.util.Assert;
|
||||
public final class PostAuthorizeReactiveAuthorizationManager
|
||||
implements ReactiveAuthorizationManager<MethodInvocationResult> {
|
||||
|
||||
private final PostAuthorizeExpressionAttributeRegistry registry;
|
||||
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||
|
||||
public PostAuthorizeReactiveAuthorizationManager() {
|
||||
this(new DefaultMethodSecurityExpressionHandler());
|
||||
@ -46,7 +46,19 @@ public final class 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
|
||||
* <p>
|
||||
* By default, this value is <code>null</code>, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +67,19 @@ public final class 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
|
||||
* <p>
|
||||
* By default, this value is <code>null</code>, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,7 +49,7 @@ import org.springframework.util.Assert;
|
||||
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 final class 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
|
||||
* <p>
|
||||
* By default, this value is <code>null</code>, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
|
||||
|
||||
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 @@ final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttr
|
||||
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<AnnotatedElement, PostFilter> lookup = findUniqueAnnotation(PostFilter.class);
|
||||
PostFilter postFilter = lookup.apply(method);
|
||||
return (postFilter != null) ? postFilter : lookup.apply(targetClass(method, targetClass));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
* <p>
|
||||
* By default, this value is <code>null</code>, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
|
||||
|
||||
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 @@ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAt
|
||||
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<AnnotatedElement, PreAuthorize> lookup = findUniqueAnnotation(PreAuthorize.class);
|
||||
PreAuthorize preAuthorize = lookup.apply(method);
|
||||
return (preAuthorize != null) ? preAuthorize : lookup.apply(targetClass(method, targetClass));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
|
||||
|
||||
private final PreAuthorizeExpressionAttributeRegistry registry;
|
||||
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
||||
|
||||
public PreAuthorizeReactiveAuthorizationManager() {
|
||||
this(new DefaultMethodSecurityExpressionHandler());
|
||||
@ -45,7 +45,19 @@ public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveA
|
||||
|
||||
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
|
||||
* <p>
|
||||
* By default, this value is <code>null</code>, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,7 +68,19 @@ public final class 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
|
||||
* <p>
|
||||
* By default, this value is <code>null</code>, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,7 +53,7 @@ import org.springframework.util.StringUtils;
|
||||
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 final class 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
|
||||
* <p>
|
||||
* By default, this value is <code>null</code>, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 @@ import org.springframework.util.Assert;
|
||||
final class PreFilterExpressionAttributeRegistry
|
||||
extends AbstractExpressionAttributeRegistry<PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute> {
|
||||
|
||||
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 @@ final class PreFilterExpressionAttributeRegistry
|
||||
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<AnnotatedElement, PreFilter> lookup = findUniqueAnnotation(PreFilter.class);
|
||||
PreFilter preFilter = lookup.apply(method);
|
||||
return (preFilter != null) ? preFilter : lookup.apply(targetClass(method, targetClass));
|
||||
}
|
||||
|
||||
static final class PreFilterExpressionAttribute extends ExpressionAttribute {
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* By default, this value is <code>true</code>.
|
||||
* @since 6.3
|
||||
*/
|
||||
public boolean isIgnoreUnknown() {
|
||||
return this.ignoreUnknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure template resolution to ignore unknown placeholders. When set to
|
||||
* <code>false</code>, template resolution will throw an exception for unknown
|
||||
* placeholders.
|
||||
* <p>
|
||||
* By default, this value is <code>true</code>.
|
||||
* @param ignoreUnknown - whether to ignore unknown placeholders parameters
|
||||
* @since 6.3
|
||||
*/
|
||||
public void setIgnoreUnknown(boolean ignoreUnknown) {
|
||||
this.ignoreUnknown = ignoreUnknown;
|
||||
}
|
||||
|
||||
}
|
@ -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<String>)
|
||||
----
|
||||
======
|
||||
|
||||
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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user