diff --git a/core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java b/core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java index 5093e1bd1d..34cd59eff3 100644 --- a/core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java +++ b/core/src/main/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScanner.java @@ -19,8 +19,10 @@ package org.springframework.security.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -29,6 +31,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.springframework.core.MethodClassKey; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; @@ -169,18 +172,15 @@ final class UniqueSecurityAnnotationScanner extends Abstra return Collections.emptyList(); } classesToSkip.add(targetClass); - try { - Method methodToUse = targetClass.getDeclaredMethod(method.getName(), method.getParameterTypes()); + Method methodToUse = findMethod(method, targetClass); + if (methodToUse != null) { List> annotations = findDirectAnnotations(methodToUse); if (!annotations.isEmpty()) { return annotations; } } - catch (NoSuchMethodException ex) { - // move on - } - List> annotations = new ArrayList<>(); - annotations.addAll(findClosestMethodAnnotations(method, targetClass.getSuperclass(), classesToSkip)); + List> annotations = new ArrayList<>( + findClosestMethodAnnotations(method, targetClass.getSuperclass(), classesToSkip)); for (Class inter : targetClass.getInterfaces()) { annotations.addAll(findClosestMethodAnnotations(method, inter, classesToSkip)); } @@ -212,4 +212,52 @@ final class UniqueSecurityAnnotationScanner extends Abstra .toList(); } + private static Method findMethod(Method method, Class targetClass) { + for (Method candidate : targetClass.getDeclaredMethods()) { + if (candidate == method) { + return candidate; + } + if (isOverride(method, candidate)) { + return candidate; + } + } + return null; + } + + private static boolean isOverride(Method rootMethod, Method candidateMethod) { + return (!Modifier.isPrivate(candidateMethod.getModifiers()) + && candidateMethod.getName().equals(rootMethod.getName()) + && hasSameParameterTypes(rootMethod, candidateMethod)); + } + + private static boolean hasSameParameterTypes(Method rootMethod, Method candidateMethod) { + if (candidateMethod.getParameterCount() != rootMethod.getParameterCount()) { + return false; + } + Class[] rootParameterTypes = rootMethod.getParameterTypes(); + Class[] candidateParameterTypes = candidateMethod.getParameterTypes(); + if (Arrays.equals(candidateParameterTypes, rootParameterTypes)) { + return true; + } + return hasSameGenericTypeParameters(rootMethod, candidateMethod, rootParameterTypes); + } + + private static boolean hasSameGenericTypeParameters(Method rootMethod, Method candidateMethod, + Class[] rootParameterTypes) { + + Class sourceDeclaringClass = rootMethod.getDeclaringClass(); + Class candidateDeclaringClass = candidateMethod.getDeclaringClass(); + if (!candidateDeclaringClass.isAssignableFrom(sourceDeclaringClass)) { + return false; + } + for (int i = 0; i < rootParameterTypes.length; i++) { + Class resolvedParameterType = ResolvableType.forMethodParameter(candidateMethod, i, sourceDeclaringClass) + .resolve(); + if (rootParameterTypes[i] != resolvedParameterType) { + return false; + } + } + return true; + } + } diff --git a/core/src/test/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScannerTests.java b/core/src/test/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScannerTests.java index b1a7a779aa..699d525e8b 100644 --- a/core/src/test/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScannerTests.java +++ b/core/src/test/java/org/springframework/security/core/annotation/UniqueSecurityAnnotationScannerTests.java @@ -251,6 +251,30 @@ public class UniqueSecurityAnnotationScannerTests { assertThat(preAuthorize).isNull(); } + // gh-16751 + @Test + void scanWhenAnnotationOnParameterizedInterfaceTheLocates() throws Exception { + Method method = MyServiceImpl.class.getDeclaredMethod("get", String.class); + PreAuthorize pre = this.scanner.scan(method, method.getDeclaringClass()); + assertThat(pre).isNotNull(); + } + + // gh-16751 + @Test + void scanWhenAnnotationOnParameterizedSuperClassThenLocates() throws Exception { + Method method = MyServiceImpl.class.getDeclaredMethod("getExt", Long.class); + PreAuthorize pre = this.scanner.scan(method, method.getDeclaringClass()); + assertThat(pre).isNotNull(); + } + + // gh-16751 + @Test + void scanWhenAnnotationOnParameterizedMethodThenLocates() throws Exception { + Method method = MyServiceImpl.class.getDeclaredMethod("getExtByClass", Class.class, Long.class); + PreAuthorize pre = this.scanner.scan(method, method.getDeclaringClass()); + assertThat(pre).isNotNull(); + } + @PreAuthorize("one") private interface AnnotationOnInterface { @@ -577,4 +601,40 @@ public class UniqueSecurityAnnotationScannerTests { } + interface MyService { + + @PreAuthorize("thirty") + C get(U u); + + } + + abstract static class MyServiceExt implements MyService { + + @PreAuthorize("thirtyone") + abstract T getExt(T t); + + @PreAuthorize("thirtytwo") + abstract S getExtByClass(Class clazz, T t); + + } + + static class MyServiceImpl extends MyServiceExt { + + @Override + public Integer get(final String s) { + return 0; + } + + @Override + Long getExt(Long o) { + return 0L; + } + + @Override + S getExtByClass(Class clazz, Long l) { + return null; + } + + } + }