Support Meta-Annotation Parameters on Parameter Annotations

Closes gh-16248
This commit is contained in:
github-actions[bot] 2024-12-09 01:10:41 +00:00 committed by Josh Cummings
parent 9ae432f0d2
commit 95ec49a21d
2 changed files with 157 additions and 1 deletions

View File

@ -18,6 +18,7 @@ package org.springframework.security.core.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Executable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.util.ArrayList; import java.util.ArrayList;
@ -83,6 +84,7 @@ import org.springframework.util.ClassUtils;
* *
* @param <A> the annotation to search for and synthesize * @param <A> the annotation to search for and synthesize
* @author Josh Cummings * @author Josh Cummings
* @author DingHao
* @since 6.4 * @since 6.4
*/ */
final class UniqueSecurityAnnotationScanner<A extends Annotation> extends AbstractSecurityAnnotationScanner<A> { final class UniqueSecurityAnnotationScanner<A extends Annotation> extends AbstractSecurityAnnotationScanner<A> {
@ -107,7 +109,7 @@ final class UniqueSecurityAnnotationScanner<A extends Annotation> extends Abstra
MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) { MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
if (element instanceof Parameter parameter) { if (element instanceof Parameter parameter) {
return this.uniqueParameterAnnotationCache.computeIfAbsent(parameter, (p) -> { return this.uniqueParameterAnnotationCache.computeIfAbsent(parameter, (p) -> {
List<MergedAnnotation<A>> annotations = findDirectAnnotations(p); List<MergedAnnotation<A>> annotations = findParameterAnnotations(p);
return requireUnique(p, annotations); return requireUnique(p, annotations);
}); });
} }
@ -137,6 +139,56 @@ final class UniqueSecurityAnnotationScanner<A extends Annotation> extends Abstra
}; };
} }
private List<MergedAnnotation<A>> findParameterAnnotations(Parameter current) {
List<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(current);
if (!directAnnotations.isEmpty()) {
return directAnnotations;
}
Executable executable = current.getDeclaringExecutable();
if (executable instanceof Method method) {
Class<?> clazz = method.getDeclaringClass();
Set<Class<?>> visited = new HashSet<>();
while (clazz != null && clazz != Object.class) {
directAnnotations = findClosestParameterAnnotations(method, clazz, current, visited);
if (!directAnnotations.isEmpty()) {
return directAnnotations;
}
clazz = clazz.getSuperclass();
}
}
return Collections.emptyList();
}
private List<MergedAnnotation<A>> findClosestParameterAnnotations(Method method, Class<?> clazz, Parameter current,
Set<Class<?>> visited) {
if (!visited.add(clazz)) {
return Collections.emptyList();
}
List<MergedAnnotation<A>> annotations = new ArrayList<>(findDirectParameterAnnotations(method, clazz, current));
for (Class<?> ifc : clazz.getInterfaces()) {
annotations.addAll(findClosestParameterAnnotations(method, ifc, current, visited));
}
return annotations;
}
private List<MergedAnnotation<A>> findDirectParameterAnnotations(Method method, Class<?> clazz, Parameter current) {
try {
Method methodToUse = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
for (Parameter parameter : methodToUse.getParameters()) {
if (parameter.getName().equals(current.getName())) {
List<MergedAnnotation<A>> directAnnotations = findDirectAnnotations(parameter);
if (!directAnnotations.isEmpty()) {
return directAnnotations;
}
}
}
}
catch (NoSuchMethodException ex) {
// move on
}
return Collections.emptyList();
}
private List<MergedAnnotation<A>> findMethodAnnotations(Method method, Class<?> targetClass) { private List<MergedAnnotation<A>> findMethodAnnotations(Method method, Class<?> targetClass) {
// The method may be on an interface, but we need attributes from the target // The method may be on an interface, but we need attributes from the target
// class. // class.

View File

@ -16,7 +16,13 @@
package org.springframework.security.core.annotation; package org.springframework.security.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -34,6 +40,9 @@ public class UniqueSecurityAnnotationScannerTests {
private UniqueSecurityAnnotationScanner<PreAuthorize> scanner = new UniqueSecurityAnnotationScanner<>( private UniqueSecurityAnnotationScanner<PreAuthorize> scanner = new UniqueSecurityAnnotationScanner<>(
PreAuthorize.class); PreAuthorize.class);
private UniqueSecurityAnnotationScanner<CustomParameterAnnotation> parameterScanner = new UniqueSecurityAnnotationScanner<>(
CustomParameterAnnotation.class);
@Test @Test
void scanWhenAnnotationOnInterfaceThenResolves() throws Exception { void scanWhenAnnotationOnInterfaceThenResolves() throws Exception {
Method method = AnnotationOnInterface.class.getDeclaredMethod("method"); Method method = AnnotationOnInterface.class.getDeclaredMethod("method");
@ -251,6 +260,101 @@ public class UniqueSecurityAnnotationScannerTests {
assertThat(preAuthorize).isNull(); assertThat(preAuthorize).isNull();
} }
@Test
void scanParameterAnnotationWhenAnnotationOnInterface() throws Exception {
Parameter parameter = UserService.class.getDeclaredMethod("add", String.class).getParameters()[0];
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
assertThat(customParameterAnnotation.value()).isEqualTo("one");
}
@Test
void scanParameterAnnotationWhenClassInheritingInterfaceAnnotation() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("add", String.class).getParameters()[0];
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
assertThat(customParameterAnnotation.value()).isEqualTo("one");
}
@Test
void scanParameterAnnotationWhenClassOverridingMethodOverridingInterface() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("get", String.class).getParameters()[0];
CustomParameterAnnotation customParameterAnnotation = this.parameterScanner.scan(parameter);
assertThat(customParameterAnnotation.value()).isEqualTo("five");
}
@Test
void scanParameterAnnotationWhenMultipleMethodInheritanceThenException() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("list", String.class).getParameters()[0];
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> this.parameterScanner.scan(parameter));
}
@Test
void scanParameterAnnotationWhenInterfaceNoAnnotationsThenException() throws Exception {
Parameter parameter = UserServiceImpl.class.getDeclaredMethod("delete", String.class).getParameters()[0];
assertThatExceptionOfType(AnnotationConfigurationException.class)
.isThrownBy(() -> this.parameterScanner.scan(parameter));
}
interface UserService {
void add(@CustomParameterAnnotation("one") String user);
List<String> list(@CustomParameterAnnotation("two") String user);
String get(@CustomParameterAnnotation("three") String user);
void delete(@CustomParameterAnnotation("five") String user);
}
interface OtherUserService {
List<String> list(@CustomParameterAnnotation("four") String user);
}
interface ThirdPartyUserService {
void delete(@CustomParameterAnnotation("five") String user);
}
interface RemoteUserService extends ThirdPartyUserService {
}
static class UserServiceImpl implements UserService, OtherUserService, RemoteUserService {
@Override
public void add(String user) {
}
@Override
public List<String> list(String user) {
return List.of(user);
}
@Override
public String get(@CustomParameterAnnotation("five") String user) {
return user;
}
@Override
public void delete(String user) {
}
}
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@interface CustomParameterAnnotation {
String value();
}
@PreAuthorize("one") @PreAuthorize("one")
private interface AnnotationOnInterface { private interface AnnotationOnInterface {