diff --git a/core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScanner.java b/core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScanner.java index 7ee6e58d74f..a6dfd1753cf 100644 --- a/core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScanner.java +++ b/core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScanner.java @@ -57,12 +57,17 @@ * {@code @HasRole} annotation found on a given {@link AnnotatedElement}. * *
+ * Meta-annotations that use enum values can use {@link ExpressionTemplateValueProvider} to + * provide custom placeholder values. + * + *
* Since the process of synthesis is expensive, it is recommended to cache the synthesized
* result to prevent multiple computations.
*
* @param the annotation to search for and synthesize
* @author Josh Cummings
* @author DingHao
+ * @author Mike Heath
* @since 6.4
*/
final class ExpressionTemplateSecurityAnnotationScanner
@@ -72,6 +77,7 @@ final class ExpressionTemplateSecurityAnnotationScanner
static {
conversionService.addConverter(new ClassToStringConverter());
+ conversionService.addConverter(new ExpressionTemplateValueProviderConverter());
}
private final Class type;
@@ -160,4 +166,18 @@ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor t
}
+ static class ExpressionTemplateValueProviderConverter implements GenericConverter {
+
+ @Override
+ public Set
+ * enum Permission implements ExpressionTemplateValueProvider {
+ * READ,
+ * WRITE;
+ *
+ * @Override
+ * public String getExpressionTemplateValue() {
+ * return switch (this) {
+ * case READ -> "user.permission-read";
+ * case WRITE -> "user.permission-write";
+ * }
+ * }
+ *
+ * }
+ *
+ *
+ * @since 6.5
+ * @author Mike Heath
+ */
+public interface ExpressionTemplateValueProvider {
+
+ /**
+ * Returns the value to be used in an expression template.
+ *
+ * @return the value to be used in an expression template
+ */
+ String getExpressionTemplateValue();
+
+}
diff --git a/core/src/test/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScannerTests.java b/core/src/test/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScannerTests.java
index 7bbb0ff20bf..8259bd99f29 100644
--- a/core/src/test/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScannerTests.java
+++ b/core/src/test/java/org/springframework/security/core/annotation/ExpressionTemplateSecurityAnnotationScannerTests.java
@@ -54,6 +54,43 @@ void parseMultipleMetaSourceAnnotationParameterWithAliasFor() throws Exception {
assertThat(preAuthorize.value()).isEqualTo("check(#name)");
}
+ @Test
+ void parseMetaSourceAnnotationWithEnumImplementingExpressionTemplateValueProvider() throws Exception {
+ Method method = MessageService.class.getDeclaredMethod("process");
+ PreAuthorize preAuthorize = this.scanner.scan(method, method.getDeclaringClass());
+ assertThat(preAuthorize.value()).isEqualTo("hasAnyAuthority(user.READ,user.WRITE)");
+ }
+
+ enum Permission implements ExpressionTemplateValueProvider {
+ READ,
+ WRITE;
+
+ @Override
+ public String getExpressionTemplateValue() {
+ return switch (this) {
+ case READ -> "user.READ";
+ case WRITE -> "user.WRITE";
+ };
+ }
+ }
+
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ ElementType.TYPE, ElementType.METHOD })
+ @PreAuthorize("hasAnyAuthority({permissions})")
+ @interface HasAnyCustomPermissions {
+
+ Permission[] permissions();
+
+ }
+
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ ElementType.TYPE, ElementType.METHOD })
+ @HasAnyCustomPermissions(permissions = { Permission.READ, Permission.WRITE })
+ @interface HasAllCustomPermissions {
+ }
+
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@@ -86,6 +123,9 @@ void parseMultipleMetaSourceAnnotationParameterWithAliasFor() throws Exception {
private interface MessageService {
+ @HasAllCustomPermissions
+ void process();
+
@HasReadPermission("#name")
String sayHello(String name);