This commit is contained in:
Mike Heath 2025-07-02 17:13:45 -06:00 committed by GitHub
commit 91e4915b05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 95 additions and 0 deletions

View File

@ -57,12 +57,17 @@ import org.springframework.util.PropertyPlaceholderHelper;
* {@code @HasRole} annotation found on a given {@link AnnotatedElement}.
*
* <p>
* Meta-annotations that use enum values can use {@link ExpressionTemplateValueProvider} to
* provide custom placeholder values.
*
* <p>
* Since the process of synthesis is expensive, it is recommended to cache the synthesized
* result to prevent multiple computations.
*
* @param <A> the annotation to search for and synthesize
* @author Josh Cummings
* @author DingHao
* @author Mike Heath
* @since 6.4
*/
final class ExpressionTemplateSecurityAnnotationScanner<A extends Annotation>
@ -72,6 +77,7 @@ final class ExpressionTemplateSecurityAnnotationScanner<A extends Annotation>
static {
conversionService.addConverter(new ClassToStringConverter());
conversionService.addConverter(new ExpressionTemplateValueProviderConverter());
}
private final Class<A> type;
@ -160,4 +166,18 @@ final class ExpressionTemplateSecurityAnnotationScanner<A extends Annotation>
}
static class ExpressionTemplateValueProviderConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(ExpressionTemplateValueProvider.class, String.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return (source != null) ? ((ExpressionTemplateValueProvider)source).getExpressionTemplateValue() : null;
}
}
}

View File

@ -0,0 +1,35 @@
package org.springframework.security.core.annotation;
/**
* Provides a mechanism for providing custom values from enum types used in security
* meta-annotation expressions. For example:
*
* <pre>
* enum Permission implements ExpressionTemplateValueProvider {
* READ,
* WRITE;
*
* &#64;Override
* public String getExpressionTemplateValue() {
* return switch (this) {
* case READ -> "user.permission-read";
* case WRITE -> "user.permission-write";
* }
* }
*
* }
* </pre>
*
* @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();
}

View File

@ -54,6 +54,43 @@ public class ExpressionTemplateSecurityAnnotationScannerTests {
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 @@ public class ExpressionTemplateSecurityAnnotationScannerTests {
private interface MessageService {
@HasAllCustomPermissions
void process();
@HasReadPermission("#name")
String sayHello(String name);