Support Meta-Annotation Parameters on Parameter Annotations
Closes gh-16248
This commit is contained in:
parent
9ae432f0d2
commit
95ec49a21d
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue