|
@@ -1,5 +1,5 @@
|
|
/*
|
|
/*
|
|
- * Copyright 2002-2023 the original author or authors.
|
|
|
|
|
|
+ * Copyright 2002-2024 the original author or authors.
|
|
*
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* you may not use this file except in compliance with the License.
|
|
@@ -17,31 +17,36 @@
|
|
package org.springframework.security.authorization.method;
|
|
package org.springframework.security.authorization.method;
|
|
|
|
|
|
import java.lang.annotation.Annotation;
|
|
import java.lang.annotation.Annotation;
|
|
-import java.lang.reflect.Executable;
|
|
|
|
|
|
+import java.lang.reflect.AnnotatedElement;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Method;
|
|
|
|
+import java.util.List;
|
|
|
|
|
|
import org.springframework.core.annotation.AnnotationConfigurationException;
|
|
import org.springframework.core.annotation.AnnotationConfigurationException;
|
|
-import org.springframework.core.annotation.AnnotationUtils;
|
|
|
|
import org.springframework.core.annotation.MergedAnnotation;
|
|
import org.springframework.core.annotation.MergedAnnotation;
|
|
import org.springframework.core.annotation.MergedAnnotations;
|
|
import org.springframework.core.annotation.MergedAnnotations;
|
|
|
|
+import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
|
import org.springframework.core.annotation.RepeatableContainers;
|
|
import org.springframework.core.annotation.RepeatableContainers;
|
|
|
|
|
|
/**
|
|
/**
|
|
- * A wrapper around {@link AnnotationUtils} that checks for, and errors on, conflicting
|
|
|
|
- * annotations. This is specifically important for Spring Security annotations which are
|
|
|
|
- * not designed to be repeatable.
|
|
|
|
|
|
+ * A collection of utility methods that check for, and error on, conflicting annotations.
|
|
|
|
+ * This is specifically important for Spring Security annotations which are not designed
|
|
|
|
+ * to be repeatable.
|
|
*
|
|
*
|
|
|
|
+ * <p>
|
|
* There are numerous ways that two annotations of the same type may be attached to the
|
|
* There are numerous ways that two annotations of the same type may be attached to the
|
|
* same method. For example, a class may implement a method defined in two separate
|
|
* same method. For example, a class may implement a method defined in two separate
|
|
- * interfaces. If both of those interfaces have a `@PreAuthorize` annotation, then it's
|
|
|
|
- * unclear which `@PreAuthorize` expression Spring Security should use.
|
|
|
|
|
|
+ * interfaces. If both of those interfaces have a {@code @PreAuthorize} annotation, then
|
|
|
|
+ * it's unclear which {@code @PreAuthorize} expression Spring Security should use.
|
|
*
|
|
*
|
|
|
|
+ * <p>
|
|
* Another way is when one of Spring Security's annotations is used as a meta-annotation.
|
|
* Another way is when one of Spring Security's annotations is used as a meta-annotation.
|
|
* In that case, two custom annotations can be declared, each with their own
|
|
* In that case, two custom annotations can be declared, each with their own
|
|
- * `@PreAuthorize` declaration. If both custom annotations are used on the same method,
|
|
|
|
- * then it's unclear which `@PreAuthorize` expression Spring Security should use.
|
|
|
|
|
|
+ * {@code @PreAuthorize} declaration. If both custom annotations are used on the same
|
|
|
|
+ * method, then it's unclear which {@code @PreAuthorize} expression Spring Security should
|
|
|
|
+ * use.
|
|
*
|
|
*
|
|
* @author Josh Cummings
|
|
* @author Josh Cummings
|
|
|
|
+ * @author Sam Brannen
|
|
*/
|
|
*/
|
|
final class AuthorizationAnnotationUtils {
|
|
final class AuthorizationAnnotationUtils {
|
|
|
|
|
|
@@ -50,23 +55,17 @@ final class AuthorizationAnnotationUtils {
|
|
* the annotation of type {@code annotationType}, including any annotations using
|
|
* the annotation of type {@code annotationType}, including any annotations using
|
|
* {@code annotationType} as a meta-annotation.
|
|
* {@code annotationType} as a meta-annotation.
|
|
*
|
|
*
|
|
- * If more than one is found, then throw an error.
|
|
|
|
|
|
+ * <p>
|
|
|
|
+ * If more than one unique annotation is found, then throw an error.
|
|
* @param method the method declaration to search from
|
|
* @param method the method declaration to search from
|
|
* @param annotationType the annotation type to search for
|
|
* @param annotationType the annotation type to search for
|
|
- * @return the unique instance of the annotation attributed to the method,
|
|
|
|
- * {@code null} otherwise
|
|
|
|
- * @throws AnnotationConfigurationException if more than one instance of the
|
|
|
|
|
|
+ * @return a unique instance of the annotation attributed to the method, {@code null}
|
|
|
|
+ * otherwise
|
|
|
|
+ * @throws AnnotationConfigurationException if more than one unique instance of the
|
|
* annotation is found
|
|
* annotation is found
|
|
*/
|
|
*/
|
|
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType) {
|
|
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType) {
|
|
- MergedAnnotations mergedAnnotations = MergedAnnotations.from(method,
|
|
|
|
- MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
|
|
|
|
- if (hasDuplicate(mergedAnnotations, annotationType)) {
|
|
|
|
- throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType
|
|
|
|
- + " attributed to " + method
|
|
|
|
- + " Please remove the duplicate annotations and publish a bean to handle your authorization logic.");
|
|
|
|
- }
|
|
|
|
- return AnnotationUtils.findAnnotation(method, annotationType);
|
|
|
|
|
|
+ return findDistinctAnnotation(method, annotationType);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -74,60 +73,38 @@ final class AuthorizationAnnotationUtils {
|
|
* the annotation of type {@code annotationType}, including any annotations using
|
|
* the annotation of type {@code annotationType}, including any annotations using
|
|
* {@code annotationType} as a meta-annotation.
|
|
* {@code annotationType} as a meta-annotation.
|
|
*
|
|
*
|
|
- * If more than one is found, then throw an error.
|
|
|
|
|
|
+ * <p>
|
|
|
|
+ * If more than one unique annotation is found, then throw an error.
|
|
* @param type the type to search from
|
|
* @param type the type to search from
|
|
* @param annotationType the annotation type to search for
|
|
* @param annotationType the annotation type to search for
|
|
- * @return the unique instance of the annotation attributed to the method,
|
|
|
|
- * {@code null} otherwise
|
|
|
|
- * @throws AnnotationConfigurationException if more than one instance of the
|
|
|
|
|
|
+ * @return a unique instance of the annotation attributed to the class, {@code null}
|
|
|
|
+ * otherwise
|
|
|
|
+ * @throws AnnotationConfigurationException if more than one unique instance of the
|
|
* annotation is found
|
|
* annotation is found
|
|
*/
|
|
*/
|
|
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType) {
|
|
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType) {
|
|
- MergedAnnotations mergedAnnotations = MergedAnnotations.from(type,
|
|
|
|
- MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
|
|
|
|
- if (hasDuplicate(mergedAnnotations, annotationType)) {
|
|
|
|
- throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType
|
|
|
|
- + " attributed to " + type
|
|
|
|
- + " Please remove the duplicate annotations and publish a bean to handle your authorization logic.");
|
|
|
|
- }
|
|
|
|
- return AnnotationUtils.findAnnotation(type, annotationType);
|
|
|
|
|
|
+ return findDistinctAnnotation(type, annotationType);
|
|
}
|
|
}
|
|
|
|
|
|
- private static <A extends Annotation> boolean hasDuplicate(MergedAnnotations mergedAnnotations,
|
|
|
|
|
|
+ private static <A extends Annotation> A findDistinctAnnotation(AnnotatedElement annotatedElement,
|
|
Class<A> annotationType) {
|
|
Class<A> annotationType) {
|
|
- MergedAnnotation<Annotation> alreadyFound = null;
|
|
|
|
- for (MergedAnnotation<Annotation> mergedAnnotation : mergedAnnotations) {
|
|
|
|
- if (isSynthetic(mergedAnnotation.getSource())) {
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (mergedAnnotation.getType() != annotationType) {
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (alreadyFound == null) {
|
|
|
|
- alreadyFound = mergedAnnotation;
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // https://github.com/spring-projects/spring-framework/issues/31803
|
|
|
|
- if (!mergedAnnotation.getSource().equals(alreadyFound.getSource())) {
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (mergedAnnotation.getRoot().getType() != alreadyFound.getRoot().getType()) {
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static boolean isSynthetic(Object object) {
|
|
|
|
- if (object instanceof Executable) {
|
|
|
|
- return ((Executable) object).isSynthetic();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return false;
|
|
|
|
|
|
+ MergedAnnotations mergedAnnotations = MergedAnnotations.from(annotatedElement, SearchStrategy.TYPE_HIERARCHY,
|
|
|
|
+ RepeatableContainers.none());
|
|
|
|
+
|
|
|
|
+ List<A> annotations = mergedAnnotations.stream(annotationType)
|
|
|
|
+ .map(MergedAnnotation::withNonMergedAttributes)
|
|
|
|
+ .map(MergedAnnotation::synthesize)
|
|
|
|
+ .distinct()
|
|
|
|
+ .toList();
|
|
|
|
+
|
|
|
|
+ return switch (annotations.size()) {
|
|
|
|
+ case 0 -> null;
|
|
|
|
+ case 1 -> annotations.get(0);
|
|
|
|
+ default -> throw new AnnotationConfigurationException("""
|
|
|
|
+ Please ensure there is one unique annotation of type @%s attributed to %s. \
|
|
|
|
+ Found %d competing annotations: %s""".formatted(annotationType.getName(), annotatedElement,
|
|
|
|
+ annotations.size(), annotations));
|
|
|
|
+ };
|
|
}
|
|
}
|
|
|
|
|
|
private AuthorizationAnnotationUtils() {
|
|
private AuthorizationAnnotationUtils() {
|