Skip to content

Commit 95ec49a

Browse files
github-actions[bot]jzheaux
authored andcommitted
Support Meta-Annotation Parameters on Parameter Annotations
Closes gh-16248
1 parent 9ae432f commit 95ec49a

File tree

2 files changed

+157
-1
lines changed

2 files changed

+157
-1
lines changed

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

+53-1
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,56 @@ 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+
Executable executable = current.getDeclaringExecutable();
148+
if (executable instanceof Method method) {
149+
Class<?> clazz = method.getDeclaringClass();
150+
Set<Class<?>> visited = new HashSet<>();
151+
while (clazz != null && clazz != Object.class) {
152+
directAnnotations = findClosestParameterAnnotations(method, clazz, current, visited);
153+
if (!directAnnotations.isEmpty()) {
154+
return directAnnotations;
155+
}
156+
clazz = clazz.getSuperclass();
157+
}
158+
}
159+
return Collections.emptyList();
160+
}
161+
162+
private List<MergedAnnotation<A>> findClosestParameterAnnotations(Method method, Class<?> clazz, Parameter current,
163+
Set<Class<?>> visited) {
164+
if (!visited.add(clazz)) {
165+
return Collections.emptyList();
166+
}
167+
List<MergedAnnotation<A>> annotations = new ArrayList<>(findDirectParameterAnnotations(method, clazz, current));
168+
for (Class<?> ifc : clazz.getInterfaces()) {
169+
annotations.addAll(findClosestParameterAnnotations(method, ifc, current, visited));
170+
}
171+
return annotations;
172+
}
173+
174+
private List<MergedAnnotation<A>> findDirectParameterAnnotations(Method method, Class<?> clazz, Parameter current) {
175+
try {
176+
Method methodToUse = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
177+
for (Parameter parameter : methodToUse.getParameters()) {
178+
if (parameter.getName().equals(current.getName())) {
179+
List<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(parameter);
180+
if (!directAnnotations.isEmpty()) {
181+
return directAnnotations;
182+
}
183+
}
184+
}
185+
}
186+
catch (NoSuchMethodException ex) {
187+
// move on
188+
}
189+
return Collections.emptyList();
190+
}
191+
140192
private List<MergedAnnotation<A>> findMethodAnnotations(Method method, Class<?> targetClass) {
141193
// The method may be on an interface, but we need attributes from the target
142194
// class.

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

+104
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,101 @@ 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+
@Test
292+
void scanParameterAnnotationWhenInterfaceNoAnnotationsThenException() throws Exception {
293+
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("delete", String.class).getParameters()[0];
294+
assertThatExceptionOfType(AnnotationConfigurationException.class)
295+
.isThrownBy(() -> this.parameterScanner.scan(parameter));
296+
}
297+
298+
interface UserService {
299+
300+
void add(@CustomParameterAnnotation("one") String user);
301+
302+
List<String> list(@CustomParameterAnnotation("two") String user);
303+
304+
String get(@CustomParameterAnnotation("three") String user);
305+
306+
void delete(@CustomParameterAnnotation("five") String user);
307+
308+
}
309+
310+
interface OtherUserService {
311+
312+
List<String> list(@CustomParameterAnnotation("four") String user);
313+
314+
}
315+
316+
interface ThirdPartyUserService {
317+
318+
void delete(@CustomParameterAnnotation("five") String user);
319+
320+
}
321+
322+
interface RemoteUserService extends ThirdPartyUserService {
323+
324+
}
325+
326+
static class UserServiceImpl implements UserService, OtherUserService, RemoteUserService {
327+
328+
@Override
329+
public void add(String user) {
330+
331+
}
332+
333+
@Override
334+
public List<String> list(String user) {
335+
return List.of(user);
336+
}
337+
338+
@Override
339+
public String get(@CustomParameterAnnotation("five") String user) {
340+
return user;
341+
}
342+
343+
@Override
344+
public void delete(String user) {
345+
346+
}
347+
348+
}
349+
350+
@Target({ ElementType.PARAMETER })
351+
@Retention(RetentionPolicy.RUNTIME)
352+
@interface CustomParameterAnnotation {
353+
354+
String value();
355+
356+
}
357+
254358
@PreAuthorize("one")
255359
private interface AnnotationOnInterface {
256360

0 commit comments

Comments
 (0)