Skip to content

Commit a6e1c16

Browse files
committed
Support Meta-Annotation Parameters on Parameter Annotations
Closes-16248
1 parent 5329030 commit a6e1c16

File tree

9 files changed

+134
-43
lines changed

9 files changed

+134
-43
lines changed

core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.lang.annotation.Annotation;
2020
import java.lang.reflect.AnnotatedElement;
21+
import java.lang.reflect.Executable;
2122
import java.lang.reflect.Method;
2223
import java.lang.reflect.Parameter;
2324
import java.util.ArrayList;
@@ -83,6 +84,7 @@
8384
*
8485
* @param <A> the annotation to search for and synthesize
8586
* @author Josh Cummings
87+
* @author DingHao
8688
* @since 6.4
8789
*/
8890
final class UniqueSecurityAnnotationScanner<A extends Annotation> extends AbstractSecurityAnnotationScanner<A> {
@@ -107,7 +109,7 @@ final class UniqueSecurityAnnotationScanner<A extends Annotation> extends Abstra
107109
MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
108110
if (element instanceof Parameter parameter) {
109111
return this.uniqueParameterAnnotationCache.computeIfAbsent(parameter, (p) -> {
110-
List<MergedAnnotation<A>> annotations = findDirectAnnotations(p);
112+
List<MergedAnnotation<A>> annotations = findParameterAnnotations(p);
111113
return requireUnique(p, annotations);
112114
});
113115
}
@@ -137,6 +139,50 @@ private MergedAnnotation<A> requireUnique(AnnotatedElement element, List<MergedA
137139
};
138140
}
139141

142+
private List<MergedAnnotation<A>> findParameterAnnotations(Parameter current) {
143+
List<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(current);
144+
if (!directAnnotations.isEmpty()) {
145+
return directAnnotations;
146+
}
147+
directAnnotations = new ArrayList<>(findDirectAnnotations(current));
148+
Executable executable = current.getDeclaringExecutable();
149+
if (executable instanceof Method method) {
150+
Class<?> clazz = method.getDeclaringClass();
151+
Set<Class<?>> visited = new HashSet<>();
152+
while (clazz != null && visited.add(clazz)) {
153+
for (Class<?> ifc : clazz.getInterfaces()) {
154+
directAnnotations.addAll(findParameterAnnotations(method, ifc, current));
155+
}
156+
clazz = clazz.getSuperclass();
157+
if (clazz == Object.class) {
158+
clazz = null;
159+
}
160+
if (clazz != null && visited.add(clazz)) {
161+
directAnnotations.addAll(findParameterAnnotations(method, clazz, current));
162+
}
163+
}
164+
}
165+
return directAnnotations;
166+
}
167+
168+
private List<MergedAnnotation<A>> findParameterAnnotations(Method method, Class<?> superOrIfc, Parameter current) {
169+
try {
170+
Method methodToUse = superOrIfc.getDeclaredMethod(method.getName(), method.getParameterTypes());
171+
for (Parameter parameter : methodToUse.getParameters()) {
172+
if (parameter.getName().equals(current.getName())) {
173+
List<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(parameter);
174+
if (!directAnnotations.isEmpty()) {
175+
return directAnnotations;
176+
}
177+
}
178+
}
179+
}
180+
catch (NoSuchMethodException ex) {
181+
// move on
182+
}
183+
return Collections.emptyList();
184+
}
185+
140186
private List<MergedAnnotation<A>> findMethodAnnotations(Method method, Class<?> targetClass) {
141187
// The method may be on an interface, but we need attributes from the target
142188
// class.

core/src/test/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScannerTests.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@
1616

1717
package org.springframework.security.core.annotation;
1818

19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
1923
import java.lang.reflect.Method;
24+
import java.lang.reflect.Parameter;
25+
import java.util.List;
2026

2127
import org.junit.jupiter.api.Test;
2228

@@ -34,6 +40,9 @@ public class UniqueSecurityAnnotationScannerTests {
3440
private UniqueSecurityAnnotationScanner<PreAuthorize> scanner = new UniqueSecurityAnnotationScanner<>(
3541
PreAuthorize.class);
3642

43+
private UniqueSecurityAnnotationScanner<CustomParameterAnnotation> parameterScanner = new UniqueSecurityAnnotationScanner<>(
44+
CustomParameterAnnotation.class);
45+
3746
@Test
3847
void scanWhenAnnotationOnInterfaceThenResolves() throws Exception {
3948
Method method = AnnotationOnInterface.class.getDeclaredMethod("method");
@@ -251,6 +260,77 @@ void scanWhenClassInheritingAbstractClassNoAnnotationsThenNoAnnotation() throws
251260
assertThat(preAuthorize).isNull();
252261
}
253262

263+
@Test
264+
void scanParameterAnnotationWhenAnnotationOnInterface() throws Exception {
265+
Parameter parameter = UserService.class.getDeclaredMethod("add", String.class).getParameters()[0];
266+
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
267+
assertThat(customParameterAnnotation.value()).isEqualTo("one");
268+
}
269+
270+
@Test
271+
void scanParameterAnnotationWhenClassInheritingInterfaceAnnotation() throws Exception {
272+
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("add", String.class).getParameters()[0];
273+
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
274+
assertThat(customParameterAnnotation.value()).isEqualTo("one");
275+
}
276+
277+
@Test
278+
void scanParameterAnnotationWhenClassOverridingMethodOverridingInterface() throws Exception {
279+
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("get", String.class).getParameters()[0];
280+
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
281+
assertThat(customParameterAnnotation.value()).isEqualTo("five");
282+
}
283+
284+
@Test
285+
void scanParameterAnnotationWhenMultipleMethodInheritanceThenException() throws Exception {
286+
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("list", String.class).getParameters()[0];
287+
assertThatExceptionOfType(AnnotationConfigurationException.class)
288+
.isThrownBy(() -> this.parameterScanner.scan(parameter));
289+
}
290+
291+
interface UserService {
292+
293+
void add(@CustomParameterAnnotation("one") String user);
294+
295+
List<String> list(@CustomParameterAnnotation("two") String user);
296+
297+
String get(@CustomParameterAnnotation("three") String user);
298+
299+
}
300+
301+
interface OtherUserService {
302+
303+
List<String> list(@CustomParameterAnnotation("four") String user);
304+
305+
}
306+
307+
static class UserServiceImpl implements UserService, OtherUserService {
308+
309+
@Override
310+
public void add(String user) {
311+
312+
}
313+
314+
@Override
315+
public List<String> list(String user) {
316+
return List.of(user);
317+
}
318+
319+
@Override
320+
public String get(@CustomParameterAnnotation("five") String user) {
321+
return user;
322+
}
323+
324+
}
325+
326+
@Target({ ElementType.PARAMETER })
327+
@Retention(RetentionPolicy.RUNTIME)
328+
@interface CustomParameterAnnotation {
329+
330+
String value();
331+
332+
}
333+
254334
@PreAuthorize("one")
255335
private interface AnnotationOnInterface {
256336

messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.security.messaging.context;
1818

1919
import java.lang.annotation.Annotation;
20-
import java.util.Map;
21-
import java.util.concurrent.ConcurrentHashMap;
2220

2321
import org.springframework.core.MethodParameter;
2422
import org.springframework.expression.Expression;
@@ -95,8 +93,6 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
9593
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
9694
.getContextHolderStrategy();
9795

98-
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
99-
10096
private ExpressionParser parser = new SpelExpressionParser();
10197

10298
private SecurityAnnotationScanner<AuthenticationPrincipal> scanner = SecurityAnnotationScanners
@@ -164,8 +160,7 @@ public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDef
164160
*/
165161
@SuppressWarnings("unchecked")
166162
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
167-
return (T) this.cachedAttributes.computeIfAbsent(parameter,
168-
(methodParameter) -> this.scanner.scan(methodParameter.getParameter()));
163+
return (T) this.scanner.scan(parameter.getParameter());
169164
}
170165

171166
}

messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.security.messaging.handler.invocation.reactive;
1818

1919
import java.lang.annotation.Annotation;
20-
import java.util.Map;
21-
import java.util.concurrent.ConcurrentHashMap;
2220

2321
import org.reactivestreams.Publisher;
2422
import reactor.core.publisher.Mono;
@@ -99,8 +97,6 @@
9997
*/
10098
public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
10199

102-
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
103-
104100
private ExpressionParser parser = new SpelExpressionParser();
105101

106102
private SecurityAnnotationScanner<AuthenticationPrincipal> scanner = SecurityAnnotationScanners
@@ -205,8 +201,7 @@ public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDef
205201
*/
206202
@SuppressWarnings("unchecked")
207203
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
208-
return (T) this.cachedAttributes.computeIfAbsent(parameter,
209-
(methodParameter) -> this.scanner.scan(methodParameter.getParameter()));
204+
return (T) this.scanner.scan(parameter.getParameter());
210205
}
211206

212207
}

messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.security.messaging.handler.invocation.reactive;
1818

1919
import java.lang.annotation.Annotation;
20-
import java.util.Map;
21-
import java.util.concurrent.ConcurrentHashMap;
2220

2321
import org.reactivestreams.Publisher;
2422
import reactor.core.publisher.Mono;
@@ -97,8 +95,6 @@
9795
*/
9896
public class CurrentSecurityContextArgumentResolver implements HandlerMethodArgumentResolver {
9997

100-
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
101-
10298
private ExpressionParser parser = new SpelExpressionParser();
10399

104100
private SecurityAnnotationScanner<CurrentSecurityContext> scanner = SecurityAnnotationScanners
@@ -222,8 +218,7 @@ public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDef
222218
*/
223219
@SuppressWarnings("unchecked")
224220
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
225-
return (T) this.cachedAttributes.computeIfAbsent(parameter,
226-
(methodParameter) -> this.scanner.scan(methodParameter.getParameter()));
221+
return (T) this.scanner.scan(parameter.getParameter());
227222
}
228223

229224
}

web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.security.web.method.annotation;
1818

1919
import java.lang.annotation.Annotation;
20-
import java.util.Map;
21-
import java.util.concurrent.ConcurrentHashMap;
2220

2321
import org.springframework.core.MethodParameter;
2422
import org.springframework.expression.BeanResolver;
@@ -98,8 +96,6 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
9896
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
9997
.getContextHolderStrategy();
10098

101-
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
102-
10399
private ExpressionParser parser = new SpelExpressionParser();
104100

105101
private SecurityAnnotationScanner<AuthenticationPrincipal> scanner = SecurityAnnotationScanners
@@ -179,8 +175,7 @@ public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDef
179175
*/
180176
@SuppressWarnings("unchecked")
181177
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
182-
return (T) this.cachedAttributes.computeIfAbsent(parameter,
183-
(methodParameter) -> this.scanner.scan(methodParameter.getParameter()));
178+
return (T) this.scanner.scan(parameter.getParameter());
184179
}
185180

186181
}

web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.security.web.method.annotation;
1818

1919
import java.lang.annotation.Annotation;
20-
import java.util.Map;
21-
import java.util.concurrent.ConcurrentHashMap;
2220

2321
import org.springframework.core.MethodParameter;
2422
import org.springframework.expression.BeanResolver;
@@ -84,8 +82,6 @@ public final class CurrentSecurityContextArgumentResolver implements HandlerMeth
8482
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
8583
.getContextHolderStrategy();
8684

87-
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
88-
8985
private ExpressionParser parser = new SpelExpressionParser();
9086

9187
private SecurityAnnotationScanner<CurrentSecurityContext> scanner = SecurityAnnotationScanners
@@ -177,8 +173,7 @@ private Object resolveSecurityContextFromAnnotation(MethodParameter parameter, C
177173
*/
178174
@SuppressWarnings("unchecked")
179175
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
180-
return (T) this.cachedAttributes.computeIfAbsent(parameter,
181-
(methodParameter) -> this.scanner.scan(methodParameter.getParameter()));
176+
return (T) this.scanner.scan(parameter.getParameter());
182177
}
183178

184179
}

web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.security.web.reactive.result.method.annotation;
1818

1919
import java.lang.annotation.Annotation;
20-
import java.util.Map;
21-
import java.util.concurrent.ConcurrentHashMap;
2220

2321
import org.reactivestreams.Publisher;
2422
import reactor.core.publisher.Mono;
@@ -53,8 +51,6 @@
5351
*/
5452
public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgumentResolverSupport {
5553

56-
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
57-
5854
private ExpressionParser parser = new SpelExpressionParser();
5955

6056
private SecurityAnnotationScanner<AuthenticationPrincipal> scanner = SecurityAnnotationScanners
@@ -149,8 +145,7 @@ public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDef
149145
*/
150146
@SuppressWarnings("unchecked")
151147
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
152-
return (T) this.cachedAttributes.computeIfAbsent(parameter,
153-
(methodParameter) -> this.scanner.scan(methodParameter.getParameter()));
148+
return (T) this.scanner.scan(parameter.getParameter());
154149
}
155150

156151
}

web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/CurrentSecurityContextArgumentResolver.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.security.web.reactive.result.method.annotation;
1818

1919
import java.lang.annotation.Annotation;
20-
import java.util.Map;
21-
import java.util.concurrent.ConcurrentHashMap;
2220

2321
import org.reactivestreams.Publisher;
2422
import reactor.core.publisher.Mono;
@@ -53,8 +51,6 @@
5351
*/
5452
public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumentResolverSupport {
5553

56-
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
57-
5854
private ExpressionParser parser = new SpelExpressionParser();
5955

6056
private SecurityAnnotationScanner<CurrentSecurityContext> scanner = SecurityAnnotationScanners
@@ -189,8 +185,7 @@ private boolean isInvalidType(MethodParameter parameter, Object reactiveSecurity
189185
*/
190186
@SuppressWarnings("unchecked")
191187
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
192-
return (T) this.cachedAttributes.computeIfAbsent(parameter,
193-
(methodParameter) -> this.scanner.scan(methodParameter.getParameter()));
188+
return (T) this.scanner.scan(parameter.getParameter());
194189
}
195190

196191
}

0 commit comments

Comments
 (0)