Skip to content

Commit 6603fad

Browse files
committed
Add meta-annotation parameter support
Closes gh-14480
1 parent 4d039e5 commit 6603fad

19 files changed

+633
-127
lines changed

config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@
4545
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
4646
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
4747
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
48+
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
4849
import org.springframework.security.config.core.GrantedAuthorityDefaults;
4950
import org.springframework.security.core.Authentication;
5051
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -68,13 +69,15 @@ final class PrePostMethodSecurityConfiguration implements ImportAware {
6869
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
6970
static MethodInterceptor preFilterAuthorizationMethodInterceptor(
7071
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
72+
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
7173
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
7274
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
7375
ObjectProvider<RoleHierarchy> roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration,
7476
ApplicationContext context) {
7577
PreFilterAuthorizationMethodInterceptor preFilter = new PreFilterAuthorizationMethodInterceptor();
7678
preFilter.setOrder(preFilter.getOrder() + configuration.interceptorOrderOffset);
7779
strategyProvider.ifAvailable(preFilter::setSecurityContextHolderStrategy);
80+
methodSecurityDefaultsProvider.ifAvailable(preFilter::setTemplateDefaults);
7881
preFilter.setExpressionHandler(new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider,
7982
defaultsProvider, roleHierarchyProvider, context));
8083
return preFilter;
@@ -84,12 +87,14 @@ static MethodInterceptor preFilterAuthorizationMethodInterceptor(
8487
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
8588
static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor(
8689
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
90+
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
8791
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
8892
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
8993
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
9094
ObjectProvider<ObservationRegistry> registryProvider, ObjectProvider<RoleHierarchy> roleHierarchyProvider,
9195
PrePostMethodSecurityConfiguration configuration, ApplicationContext context) {
9296
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
97+
methodSecurityDefaultsProvider.ifAvailable(manager::setTemplateDefaults);
9398
manager.setExpressionHandler(new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider,
9499
defaultsProvider, roleHierarchyProvider, context));
95100
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor
@@ -104,12 +109,14 @@ static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor(
104109
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
105110
static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor(
106111
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
112+
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
107113
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
108114
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
109115
ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
110116
ObjectProvider<ObservationRegistry> registryProvider, ObjectProvider<RoleHierarchy> roleHierarchyProvider,
111117
PrePostMethodSecurityConfiguration configuration, ApplicationContext context) {
112118
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
119+
methodSecurityDefaultsProvider.ifAvailable(manager::setTemplateDefaults);
113120
manager.setExpressionHandler(new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider,
114121
defaultsProvider, roleHierarchyProvider, context));
115122
AuthorizationManagerAfterMethodInterceptor postAuthorize = AuthorizationManagerAfterMethodInterceptor
@@ -124,13 +131,15 @@ static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor(
124131
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
125132
static MethodInterceptor postFilterAuthorizationMethodInterceptor(
126133
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
134+
ObjectProvider<PrePostTemplateDefaults> methodSecurityDefaultsProvider,
127135
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
128136
ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
129137
ObjectProvider<RoleHierarchy> roleHierarchyProvider, PrePostMethodSecurityConfiguration configuration,
130138
ApplicationContext context) {
131139
PostFilterAuthorizationMethodInterceptor postFilter = new PostFilterAuthorizationMethodInterceptor();
132140
postFilter.setOrder(postFilter.getOrder() + configuration.interceptorOrderOffset);
133141
strategyProvider.ifAvailable(postFilter::setSecurityContextHolderStrategy);
142+
methodSecurityDefaultsProvider.ifAvailable(postFilter::setTemplateDefaults);
134143
postFilter.setExpressionHandler(new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider,
135144
defaultsProvider, roleHierarchyProvider, context));
136145
return postFilter;

config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java

+28-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
3636
import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor;
3737
import org.springframework.security.authorization.method.PreAuthorizeReactiveAuthorizationManager;
3838
import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
39+
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
3940
import org.springframework.security.config.core.GrantedAuthorityDefaults;
4041

4142
/**
@@ -50,32 +51,48 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration {
5051
@Bean
5152
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
5253
static PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor(
53-
MethodSecurityExpressionHandler expressionHandler) {
54-
return new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
54+
MethodSecurityExpressionHandler expressionHandler,
55+
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider) {
56+
PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor(
57+
expressionHandler);
58+
defaultsObjectProvider.ifAvailable(interceptor::setTemplateDefaults);
59+
return interceptor;
5560
}
5661

5762
@Bean
5863
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
5964
static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor(
60-
MethodSecurityExpressionHandler expressionHandler, ObjectProvider<ObservationRegistry> registryProvider) {
61-
ReactiveAuthorizationManager<MethodInvocation> authorizationManager = manager(
62-
new PreAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider);
65+
MethodSecurityExpressionHandler expressionHandler,
66+
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider,
67+
ObjectProvider<ObservationRegistry> registryProvider) {
68+
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager(
69+
expressionHandler);
70+
defaultsObjectProvider.ifAvailable(manager::setTemplateDefaults);
71+
ReactiveAuthorizationManager<MethodInvocation> authorizationManager = manager(manager, registryProvider);
6372
return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager);
6473
}
6574

6675
@Bean
6776
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
6877
static PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor(
69-
MethodSecurityExpressionHandler expressionHandler) {
70-
return new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler);
78+
MethodSecurityExpressionHandler expressionHandler,
79+
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider) {
80+
PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor(
81+
expressionHandler);
82+
defaultsObjectProvider.ifAvailable(interceptor::setTemplateDefaults);
83+
return interceptor;
7184
}
7285

7386
@Bean
7487
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
7588
static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor(
76-
MethodSecurityExpressionHandler expressionHandler, ObjectProvider<ObservationRegistry> registryProvider) {
77-
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager = manager(
78-
new PostAuthorizeReactiveAuthorizationManager(expressionHandler), registryProvider);
89+
MethodSecurityExpressionHandler expressionHandler,
90+
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider,
91+
ObjectProvider<ObservationRegistry> registryProvider) {
92+
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager(
93+
expressionHandler);
94+
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager = manager(manager, registryProvider);
95+
defaultsObjectProvider.ifAvailable(manager::setTemplateDefaults);
7996
return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager);
8097
}
8198

config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

+171
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.security.config.annotation.method.configuration;
1818

1919
import java.io.Serializable;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
2022
import java.util.ArrayList;
2123
import java.util.Arrays;
2224
import java.util.List;
@@ -49,12 +51,17 @@
4951
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
5052
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
5153
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
54+
import org.springframework.security.access.prepost.PostAuthorize;
55+
import org.springframework.security.access.prepost.PostFilter;
56+
import org.springframework.security.access.prepost.PreAuthorize;
57+
import org.springframework.security.access.prepost.PreFilter;
5258
import org.springframework.security.authorization.AuthorizationDecision;
5359
import org.springframework.security.authorization.AuthorizationEventPublisher;
5460
import org.springframework.security.authorization.AuthorizationManager;
5561
import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
5662
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
5763
import org.springframework.security.authorization.method.MethodInvocationResult;
64+
import org.springframework.security.authorization.method.PrePostTemplateDefaults;
5865
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
5966
import org.springframework.security.config.core.GrantedAuthorityDefaults;
6067
import org.springframework.security.config.test.SpringTestContext;
@@ -587,6 +594,74 @@ public void allAnnotationsWhenAdviceAfterAllOffsetThenReturnsFilteredList() {
587594
assertThat(filtered).containsExactly("DoNotDrop");
588595
}
589596

597+
@Test
598+
@WithMockUser
599+
public void methodeWhenParameterizedPreAuthorizeMetaAnnotationThenPasses() {
600+
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
601+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
602+
assertThat(service.hasRole("USER")).isTrue();
603+
}
604+
605+
@Test
606+
@WithMockUser
607+
public void methodRoleWhenPreAuthorizeMetaAnnotationHardcodedParameterThenPasses() {
608+
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
609+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
610+
assertThat(service.hasUserRole()).isTrue();
611+
}
612+
613+
@Test
614+
public void methodWhenParameterizedAnnotationThenFails() {
615+
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
616+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
617+
assertThatExceptionOfType(IllegalArgumentException.class)
618+
.isThrownBy(service::placeholdersOnlyResolvedByMetaAnnotations);
619+
}
620+
621+
@Test
622+
@WithMockUser(authorities = "SCOPE_message:read")
623+
public void methodWhenMultiplePlaceholdersHasAuthorityThenPasses() {
624+
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
625+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
626+
assertThat(service.readMessage()).isEqualTo("message");
627+
}
628+
629+
@Test
630+
@WithMockUser(roles = "ADMIN")
631+
public void methodWhenMultiplePlaceholdersHasRoleThenPasses() {
632+
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
633+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
634+
assertThat(service.readMessage()).isEqualTo("message");
635+
}
636+
637+
@Test
638+
@WithMockUser
639+
public void methodWhenPostAuthorizeMetaAnnotationThenAuthorizes() {
640+
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
641+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
642+
service.startsWithDave("daveMatthews");
643+
assertThatExceptionOfType(AccessDeniedException.class)
644+
.isThrownBy(() -> service.startsWithDave("jenniferHarper"));
645+
}
646+
647+
@Test
648+
@WithMockUser
649+
public void methodWhenPreFilterMetaAnnotationThenFilters() {
650+
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
651+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
652+
assertThat(service.parametersContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul"))))
653+
.containsExactly("dave");
654+
}
655+
656+
@Test
657+
@WithMockUser
658+
public void methodWhenPostFilterMetaAnnotationThenFilters() {
659+
this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire();
660+
MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class);
661+
assertThat(service.resultsContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul"))))
662+
.containsExactly("dave");
663+
}
664+
590665
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
591666
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
592667
}
@@ -890,4 +965,100 @@ Authz authz() {
890965

891966
}
892967

968+
@Configuration
969+
@EnableMethodSecurity
970+
static class MetaAnnotationPlaceholderConfig {
971+
972+
@Bean
973+
PrePostTemplateDefaults methodSecurityDefaults() {
974+
return new PrePostTemplateDefaults();
975+
}
976+
977+
@Bean
978+
MetaAnnotationService methodSecurityService() {
979+
return new MetaAnnotationService();
980+
}
981+
982+
}
983+
984+
static class MetaAnnotationService {
985+
986+
@RequireRole(role = "#role")
987+
boolean hasRole(String role) {
988+
return true;
989+
}
990+
991+
@RequireRole(role = "'USER'")
992+
boolean hasUserRole() {
993+
return true;
994+
}
995+
996+
@PreAuthorize("hasRole({role})")
997+
void placeholdersOnlyResolvedByMetaAnnotations() {
998+
}
999+
1000+
@HasClaim(claim = "message:read", roles = { "'ADMIN'" })
1001+
String readMessage() {
1002+
return "message";
1003+
}
1004+
1005+
@ResultStartsWith("dave")
1006+
String startsWithDave(String value) {
1007+
return value;
1008+
}
1009+
1010+
@ParameterContains("dave")
1011+
List<String> parametersContainDave(List<String> list) {
1012+
return list;
1013+
}
1014+
1015+
@ResultContains("dave")
1016+
List<String> resultsContainDave(List<String> list) {
1017+
return list;
1018+
}
1019+
1020+
}
1021+
1022+
@Retention(RetentionPolicy.RUNTIME)
1023+
@PreAuthorize("hasRole({role})")
1024+
@interface RequireRole {
1025+
1026+
String role();
1027+
1028+
}
1029+
1030+
@Retention(RetentionPolicy.RUNTIME)
1031+
@PreAuthorize("hasAuthority('SCOPE_{claim}') || hasAnyRole({roles})")
1032+
@interface HasClaim {
1033+
1034+
String claim();
1035+
1036+
String[] roles() default {};
1037+
1038+
}
1039+
1040+
@Retention(RetentionPolicy.RUNTIME)
1041+
@PostAuthorize("returnObject.startsWith('{value}')")
1042+
@interface ResultStartsWith {
1043+
1044+
String value();
1045+
1046+
}
1047+
1048+
@Retention(RetentionPolicy.RUNTIME)
1049+
@PreFilter("filterObject.contains('{value}')")
1050+
@interface ParameterContains {
1051+
1052+
String value();
1053+
1054+
}
1055+
1056+
@Retention(RetentionPolicy.RUNTIME)
1057+
@PostFilter("filterObject.contains('{value}')")
1058+
@interface ResultContains {
1059+
1060+
String value();
1061+
1062+
}
1063+
8931064
}

0 commit comments

Comments
 (0)