Browse Source

Improve @AuthenticationPrincipal meta-annotations

Closes gh-15286
DingHao 1 year ago
parent
commit
9aaf959400

+ 30 - 17
messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java

@@ -17,9 +17,10 @@
 package org.springframework.security.messaging.context;
 
 import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.springframework.core.MethodParameter;
-import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.expression.Expression;
 import org.springframework.expression.ExpressionParser;
 import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -27,6 +28,9 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.messaging.Message;
 import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.annotation.AnnotationSynthesizer;
+import org.springframework.security.core.annotation.AnnotationSynthesizers;
+import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -83,6 +87,7 @@ import org.springframework.util.StringUtils;
  * </pre>
  *
  * @author Rob Winch
+ * @author DingHao
  * @since 4.0
  */
 public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
@@ -90,11 +95,16 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
 	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
 		.getContextHolderStrategy();
 
+	private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
+
 	private ExpressionParser parser = new SpelExpressionParser();
 
+	private AnnotationSynthesizer<AuthenticationPrincipal> synthesizer = AnnotationSynthesizers
+		.requireUnique(AuthenticationPrincipal.class);
+
 	@Override
 	public boolean supportsParameter(MethodParameter parameter) {
-		return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
+		return findMethodAnnotation(parameter) != null;
 	}
 
 	@Override
@@ -104,7 +114,7 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
 			return null;
 		}
 		Object principal = authentication.getPrincipal();
-		AuthenticationPrincipal authPrincipal = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
+		AuthenticationPrincipal authPrincipal = findMethodAnnotation(parameter);
 		String expressionToParse = authPrincipal.expression();
 		if (StringUtils.hasLength(expressionToParse)) {
 			StandardEvaluationContext context = new StandardEvaluationContext();
@@ -133,26 +143,29 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
 		this.securityContextHolderStrategy = securityContextHolderStrategy;
 	}
 
+	/**
+	 * Configure AuthenticationPrincipal template resolution
+	 * <p>
+	 * By default, this value is <code>null</code>, which indicates that templates should
+	 * not be resolved.
+	 * @param templateDefaults - whether to resolve AuthenticationPrincipal templates
+	 * parameters
+	 * @since 6.4
+	 */
+	public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
+		this.synthesizer = AnnotationSynthesizers.requireUnique(AuthenticationPrincipal.class, templateDefaults);
+	}
+
 	/**
 	 * Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
-	 * @param annotationClass the class of the {@link Annotation} to find on the
 	 * {@link MethodParameter}
 	 * @param parameter the {@link MethodParameter} to search for an {@link Annotation}
 	 * @return the {@link Annotation} that was found or null.
 	 */
-	private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
-		T annotation = parameter.getParameterAnnotation(annotationClass);
-		if (annotation != null) {
-			return annotation;
-		}
-		Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
-		for (Annotation toSearch : annotationsToSearch) {
-			annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
-			if (annotation != null) {
-				return annotation;
-			}
-		}
-		return null;
+	@SuppressWarnings("unchecked")
+	private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
+		return (T) this.cachedAttributes.computeIfAbsent(parameter,
+				(methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter()));
 	}
 
 }

+ 30 - 17
messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java

@@ -17,6 +17,8 @@
 package org.springframework.security.messaging.handler.invocation.reactive;
 
 import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.reactivestreams.Publisher;
 import reactor.core.publisher.Mono;
@@ -25,7 +27,6 @@ import org.springframework.core.MethodParameter;
 import org.springframework.core.ReactiveAdapter;
 import org.springframework.core.ReactiveAdapterRegistry;
 import org.springframework.core.ResolvableType;
-import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.expression.BeanResolver;
 import org.springframework.expression.Expression;
 import org.springframework.expression.ExpressionParser;
@@ -34,6 +35,9 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.messaging.Message;
 import org.springframework.messaging.handler.invocation.reactive.HandlerMethodArgumentResolver;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.annotation.AnnotationSynthesizer;
+import org.springframework.security.core.annotation.AnnotationSynthesizers;
+import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.context.ReactiveSecurityContextHolder;
 import org.springframework.security.core.context.SecurityContext;
@@ -90,12 +94,18 @@ import org.springframework.util.StringUtils;
  * </pre>
  *
  * @author Rob Winch
+ * @author DingHao
  * @since 5.2
  */
 public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
 
+	private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
+
 	private ExpressionParser parser = new SpelExpressionParser();
 
+	private AnnotationSynthesizer<AuthenticationPrincipal> synthesizer = AnnotationSynthesizers
+		.requireUnique(AuthenticationPrincipal.class);
+
 	private BeanResolver beanResolver;
 
 	private ReactiveAdapterRegistry adapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
@@ -120,7 +130,7 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg
 
 	@Override
 	public boolean supportsParameter(MethodParameter parameter) {
-		return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
+		return findMethodAnnotation(parameter) != null;
 	}
 
 	@Override
@@ -138,7 +148,7 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg
 	}
 
 	private Object resolvePrincipal(MethodParameter parameter, Object principal) {
-		AuthenticationPrincipal authPrincipal = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
+		AuthenticationPrincipal authPrincipal = findMethodAnnotation(parameter);
 		String expressionToParse = authPrincipal.expression();
 		if (StringUtils.hasLength(expressionToParse)) {
 			StandardEvaluationContext context = new StandardEvaluationContext();
@@ -174,26 +184,29 @@ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArg
 		return !ClassUtils.isAssignable(typeToCheck, principal.getClass());
 	}
 
+	/**
+	 * Configure AuthenticationPrincipal template resolution
+	 * <p>
+	 * By default, this value is <code>null</code>, which indicates that templates should
+	 * not be resolved.
+	 * @param templateDefaults - whether to resolve AuthenticationPrincipal templates
+	 * parameters
+	 * @since 6.4
+	 */
+	public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
+		this.synthesizer = AnnotationSynthesizers.requireUnique(AuthenticationPrincipal.class, templateDefaults);
+	}
+
 	/**
 	 * Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
-	 * @param annotationClass the class of the {@link Annotation} to find on the
 	 * {@link MethodParameter}
 	 * @param parameter the {@link MethodParameter} to search for an {@link Annotation}
 	 * @return the {@link Annotation} that was found or null.
 	 */
-	private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
-		T annotation = parameter.getParameterAnnotation(annotationClass);
-		if (annotation != null) {
-			return annotation;
-		}
-		Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
-		for (Annotation toSearch : annotationsToSearch) {
-			annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
-			if (annotation != null) {
-				return annotation;
-			}
-		}
-		return null;
+	@SuppressWarnings("unchecked")
+	private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
+		return (T) this.cachedAttributes.computeIfAbsent(parameter,
+				(methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter()));
 	}
 
 }

+ 54 - 0
messaging/src/test/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolverTests.java

@@ -27,7 +27,9 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AliasFor;
 import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -167,6 +169,23 @@ public class AuthenticationPrincipalArgumentResolverTests {
 		assertThat(this.resolver.resolveArgument(showUserAnnotationObject(), null)).isEqualTo(this.expectedPrincipal);
 	}
 
+	@Test
+	public void resolveArgumentCustomMetaAnnotation() throws Exception {
+		CustomUserPrincipal principal = new CustomUserPrincipal();
+		setAuthenticationPrincipal(principal);
+		this.expectedPrincipal = principal.id;
+		assertThat(this.resolver.resolveArgument(showUserCustomMetaAnnotation(), null)).isEqualTo(principal.id);
+	}
+
+	@Test
+	public void resolveArgumentCustomMetaAnnotationTpl() throws Exception {
+		CustomUserPrincipal principal = new CustomUserPrincipal();
+		setAuthenticationPrincipal(principal);
+		this.resolver.setTemplateDefaults(new AnnotationTemplateExpressionDefaults());
+		this.expectedPrincipal = principal.id;
+		assertThat(this.resolver.resolveArgument(showUserCustomMetaAnnotationTpl(), null)).isEqualTo(principal.id);
+	}
+
 	private MethodParameter showUserNoAnnotation() {
 		return getMethodParameter("showUserNoAnnotation", String.class);
 	}
@@ -195,6 +214,14 @@ public class AuthenticationPrincipalArgumentResolverTests {
 		return getMethodParameter("showUserCustomAnnotation", CustomUserPrincipal.class);
 	}
 
+	private MethodParameter showUserCustomMetaAnnotation() {
+		return getMethodParameter("showUserCustomMetaAnnotation", int.class);
+	}
+
+	private MethodParameter showUserCustomMetaAnnotationTpl() {
+		return getMethodParameter("showUserCustomMetaAnnotationTpl", int.class);
+	}
+
 	private MethodParameter showUserSpel() {
 		return getMethodParameter("showUserSpel", String.class);
 	}
@@ -236,6 +263,23 @@ public class AuthenticationPrincipalArgumentResolverTests {
 
 	}
 
+	@Retention(RetentionPolicy.RUNTIME)
+	@AuthenticationPrincipal
+	public @interface CurrentUser2 {
+
+		@AliasFor(annotation = AuthenticationPrincipal.class)
+		String expression() default "";
+
+	}
+
+	@Retention(RetentionPolicy.RUNTIME)
+	@AuthenticationPrincipal(expression = "principal.{property}")
+	public @interface CurrentUser3 {
+
+		String property() default "";
+
+	}
+
 	public static class TestController {
 
 		public void showUserNoAnnotation(String user) {
@@ -260,6 +304,12 @@ public class AuthenticationPrincipalArgumentResolverTests {
 		public void showUserCustomAnnotation(@CurrentUser CustomUserPrincipal user) {
 		}
 
+		public void showUserCustomMetaAnnotation(@CurrentUser2(expression = "principal.id") int userId) {
+		}
+
+		public void showUserCustomMetaAnnotationTpl(@CurrentUser3(property = "id") int userId) {
+		}
+
 		public void showUserAnnotation(@AuthenticationPrincipal Object user) {
 		}
 
@@ -281,6 +331,10 @@ public class AuthenticationPrincipalArgumentResolverTests {
 
 		public final int id = 1;
 
+		public Object getPrincipal() {
+			return this;
+		}
+
 	}
 
 	public static class CopyUserPrincipal {

+ 48 - 0
messaging/src/test/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolverTests.java

@@ -23,10 +23,12 @@ import org.junit.jupiter.api.Test;
 import reactor.core.publisher.Mono;
 
 import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AliasFor;
 import org.springframework.core.annotation.SynthesizingMethodParameter;
 import org.springframework.security.authentication.TestAuthentication;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.context.ReactiveSecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -141,10 +143,56 @@ public class AuthenticationPrincipalArgumentResolverTests {
 
 	}
 
+	@Test
+	public void resolveArgumentCustomMetaAnnotation() {
+		CustomUserPrincipal principal = new CustomUserPrincipal();
+		Mono<Object> result = this.resolver.resolveArgument(arg0("showUserCustomMetaAnnotation"), null)
+			.contextWrite(ReactiveSecurityContextHolder
+				.withAuthentication(new TestingAuthenticationToken(principal, "password", "ROLE_USER")));
+		assertThat(result.block()).isEqualTo(principal.id);
+	}
+
+	@Test
+	public void resolveArgumentCustomMetaAnnotationTpl() {
+		CustomUserPrincipal principal = new CustomUserPrincipal();
+		this.resolver.setTemplateDefaults(new AnnotationTemplateExpressionDefaults());
+		Mono<Object> result = this.resolver.resolveArgument(arg0("showUserCustomMetaAnnotationTpl"), null)
+			.contextWrite(ReactiveSecurityContextHolder
+				.withAuthentication(new TestingAuthenticationToken(principal, "password", "ROLE_USER")));
+		assertThat(result.block()).isEqualTo(principal.id);
+	}
+
+	public void showUserCustomMetaAnnotation(@CurrentUser2(expression = "principal.id") int userId) {
+	}
+
+	public void showUserCustomMetaAnnotationTpl(@CurrentUser3(property = "id") int userId) {
+	}
+
 	static class CustomUserPrincipal {
 
 		public final int id = 1;
 
+		public Object getPrincipal() {
+			return this;
+		}
+
+	}
+
+	@Retention(RetentionPolicy.RUNTIME)
+	@AuthenticationPrincipal
+	public @interface CurrentUser2 {
+
+		@AliasFor(annotation = AuthenticationPrincipal.class)
+		String expression() default "";
+
+	}
+
+	@Retention(RetentionPolicy.RUNTIME)
+	@AuthenticationPrincipal(expression = "principal.{property}")
+	public @interface CurrentUser3 {
+
+		String property() default "";
+
 	}
 
 }

+ 30 - 17
web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java

@@ -17,15 +17,19 @@
 package org.springframework.security.web.method.annotation;
 
 import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.springframework.core.MethodParameter;
-import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.expression.BeanResolver;
 import org.springframework.expression.Expression;
 import org.springframework.expression.ExpressionParser;
 import org.springframework.expression.spel.standard.SpelExpressionParser;
 import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.annotation.AnnotationSynthesizer;
+import org.springframework.security.core.annotation.AnnotationSynthesizers;
+import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -86,6 +90,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
  * </pre>
  *
  * @author Rob Winch
+ * @author DingHao
  * @since 4.0
  */
 public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
@@ -93,13 +98,18 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
 	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
 		.getContextHolderStrategy();
 
+	private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
+
 	private ExpressionParser parser = new SpelExpressionParser();
 
+	private AnnotationSynthesizer<AuthenticationPrincipal> synthesizer = AnnotationSynthesizers
+		.requireUnique(AuthenticationPrincipal.class);
+
 	private BeanResolver beanResolver;
 
 	@Override
 	public boolean supportsParameter(MethodParameter parameter) {
-		return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
+		return findMethodAnnotation(parameter) != null;
 	}
 
 	@Override
@@ -110,7 +120,7 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
 			return null;
 		}
 		Object principal = authentication.getPrincipal();
-		AuthenticationPrincipal annotation = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
+		AuthenticationPrincipal annotation = findMethodAnnotation(parameter);
 		String expressionToParse = annotation.expression();
 		if (StringUtils.hasLength(expressionToParse)) {
 			StandardEvaluationContext context = new StandardEvaluationContext();
@@ -148,26 +158,29 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
 		this.securityContextHolderStrategy = securityContextHolderStrategy;
 	}
 
+	/**
+	 * Configure AuthenticationPrincipal template resolution
+	 * <p>
+	 * By default, this value is <code>null</code>, which indicates that templates should
+	 * not be resolved.
+	 * @param templateDefaults - whether to resolve AuthenticationPrincipal templates
+	 * parameters
+	 * @since 6.4
+	 */
+	public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
+		this.synthesizer = AnnotationSynthesizers.requireUnique(AuthenticationPrincipal.class, templateDefaults);
+	}
+
 	/**
 	 * Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
-	 * @param annotationClass the class of the {@link Annotation} to find on the
 	 * {@link MethodParameter}
 	 * @param parameter the {@link MethodParameter} to search for an {@link Annotation}
 	 * @return the {@link Annotation} that was found or null.
 	 */
-	private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
-		T annotation = parameter.getParameterAnnotation(annotationClass);
-		if (annotation != null) {
-			return annotation;
-		}
-		Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
-		for (Annotation toSearch : annotationsToSearch) {
-			annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
-			if (annotation != null) {
-				return annotation;
-			}
-		}
-		return null;
+	@SuppressWarnings("unchecked")
+	private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
+		return (T) this.cachedAttributes.computeIfAbsent(parameter,
+				(methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter()));
 	}
 
 }

+ 30 - 17
web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java

@@ -17,6 +17,8 @@
 package org.springframework.security.web.reactive.result.method.annotation;
 
 import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.reactivestreams.Publisher;
 import reactor.core.publisher.Mono;
@@ -25,12 +27,14 @@ import org.springframework.core.MethodParameter;
 import org.springframework.core.ReactiveAdapter;
 import org.springframework.core.ReactiveAdapterRegistry;
 import org.springframework.core.ResolvableType;
-import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.expression.BeanResolver;
 import org.springframework.expression.Expression;
 import org.springframework.expression.ExpressionParser;
 import org.springframework.expression.spel.standard.SpelExpressionParser;
 import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.security.core.annotation.AnnotationSynthesizer;
+import org.springframework.security.core.annotation.AnnotationSynthesizers;
+import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.context.ReactiveSecurityContextHolder;
 import org.springframework.security.core.context.SecurityContext;
@@ -44,12 +48,18 @@ import org.springframework.web.server.ServerWebExchange;
  * Resolves the Authentication
  *
  * @author Rob Winch
+ * @author DingHao
  * @since 5.0
  */
 public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgumentResolverSupport {
 
+	private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
+
 	private ExpressionParser parser = new SpelExpressionParser();
 
+	private AnnotationSynthesizer<AuthenticationPrincipal> synthesizer = AnnotationSynthesizers
+		.requireUnique(AuthenticationPrincipal.class);
+
 	private BeanResolver beanResolver;
 
 	public AuthenticationPrincipalArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
@@ -66,7 +76,7 @@ public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgume
 
 	@Override
 	public boolean supportsParameter(MethodParameter parameter) {
-		return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
+		return findMethodAnnotation(parameter) != null;
 	}
 
 	@Override
@@ -82,7 +92,7 @@ public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgume
 	}
 
 	private Object resolvePrincipal(MethodParameter parameter, Object principal) {
-		AuthenticationPrincipal annotation = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
+		AuthenticationPrincipal annotation = findMethodAnnotation(parameter);
 		String expressionToParse = annotation.expression();
 		if (StringUtils.hasLength(expressionToParse)) {
 			StandardEvaluationContext context = new StandardEvaluationContext();
@@ -118,26 +128,29 @@ public class AuthenticationPrincipalArgumentResolver extends HandlerMethodArgume
 		return !ClassUtils.isAssignable(typeToCheck, principal.getClass());
 	}
 
+	/**
+	 * Configure AuthenticationPrincipal template resolution
+	 * <p>
+	 * By default, this value is <code>null</code>, which indicates that templates should
+	 * not be resolved.
+	 * @param templateDefaults - whether to resolve AuthenticationPrincipal templates
+	 * parameters
+	 * @since 6.4
+	 */
+	public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
+		this.synthesizer = AnnotationSynthesizers.requireUnique(AuthenticationPrincipal.class, templateDefaults);
+	}
+
 	/**
 	 * Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
-	 * @param annotationClass the class of the {@link Annotation} to find on the
 	 * {@link MethodParameter}
 	 * @param parameter the {@link MethodParameter} to search for an {@link Annotation}
 	 * @return the {@link Annotation} that was found or null.
 	 */
-	private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
-		T annotation = parameter.getParameterAnnotation(annotationClass);
-		if (annotation != null) {
-			return annotation;
-		}
-		Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
-		for (Annotation toSearch : annotationsToSearch) {
-			annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
-			if (annotation != null) {
-				return annotation;
-			}
-		}
-		return null;
+	@SuppressWarnings("unchecked")
+	private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
+		return (T) this.cachedAttributes.computeIfAbsent(parameter,
+				(methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter()));
 	}
 
 }

+ 56 - 0
web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java

@@ -27,8 +27,10 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AliasFor;
 import org.springframework.expression.BeanResolver;
 import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -193,6 +195,25 @@ public class AuthenticationPrincipalArgumentResolverTests {
 			.isEqualTo(this.expectedPrincipal);
 	}
 
+	@Test
+	public void resolveArgumentCustomMetaAnnotation() throws Exception {
+		CustomUserPrincipal principal = new CustomUserPrincipal();
+		setAuthenticationPrincipal(principal);
+		this.expectedPrincipal = principal.id;
+		assertThat(this.resolver.resolveArgument(showUserCustomMetaAnnotation(), null, null, null))
+			.isEqualTo(this.expectedPrincipal);
+	}
+
+	@Test
+	public void resolveArgumentCustomMetaAnnotationTpl() throws Exception {
+		CustomUserPrincipal principal = new CustomUserPrincipal();
+		setAuthenticationPrincipal(principal);
+		this.resolver.setTemplateDefaults(new AnnotationTemplateExpressionDefaults());
+		this.expectedPrincipal = principal.id;
+		assertThat(this.resolver.resolveArgument(showUserCustomMetaAnnotationTpl(), null, null, null))
+			.isEqualTo(this.expectedPrincipal);
+	}
+
 	private MethodParameter showUserNoAnnotation() {
 		return getMethodParameter("showUserNoAnnotation", String.class);
 	}
@@ -241,6 +262,14 @@ public class AuthenticationPrincipalArgumentResolverTests {
 		return getMethodParameter("showUserAnnotation", Object.class);
 	}
 
+	private MethodParameter showUserCustomMetaAnnotation() {
+		return getMethodParameter("showUserCustomMetaAnnotation", int.class);
+	}
+
+	private MethodParameter showUserCustomMetaAnnotationTpl() {
+		return getMethodParameter("showUserCustomMetaAnnotationTpl", int.class);
+	}
+
 	private MethodParameter getMethodParameter(String methodName, Class<?>... paramTypes) {
 		Method method = ReflectionUtils.findMethod(TestController.class, methodName, paramTypes);
 		return new MethodParameter(method, 0);
@@ -266,6 +295,23 @@ public class AuthenticationPrincipalArgumentResolverTests {
 
 	}
 
+	@Retention(RetentionPolicy.RUNTIME)
+	@AuthenticationPrincipal
+	public @interface CurrentUser2 {
+
+		@AliasFor(annotation = AuthenticationPrincipal.class)
+		String expression() default "";
+
+	}
+
+	@Retention(RetentionPolicy.RUNTIME)
+	@AuthenticationPrincipal(expression = "principal.{property}")
+	public @interface CurrentUser3 {
+
+		String property() default "";
+
+	}
+
 	public static class TestController {
 
 		public void showUserNoAnnotation(String user) {
@@ -290,6 +336,12 @@ public class AuthenticationPrincipalArgumentResolverTests {
 		public void showUserCustomAnnotation(@CurrentUser CustomUserPrincipal user) {
 		}
 
+		public void showUserCustomMetaAnnotation(@CurrentUser2(expression = "principal.id") int userId) {
+		}
+
+		public void showUserCustomMetaAnnotationTpl(@CurrentUser3(property = "id") int userId) {
+		}
+
 		public void showUserAnnotation(@AuthenticationPrincipal Object user) {
 		}
 
@@ -314,6 +366,10 @@ public class AuthenticationPrincipalArgumentResolverTests {
 
 		public final int id = 1;
 
+		public Object getPrincipal() {
+			return this;
+		}
+
 	}
 
 	public static class CopyUserPrincipal {

+ 62 - 0
web/src/test/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolverTests.java

@@ -31,8 +31,11 @@ import reactor.core.publisher.Mono;
 
 import org.springframework.core.MethodParameter;
 import org.springframework.core.ReactiveAdapterRegistry;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
 import org.springframework.expression.BeanResolver;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.context.ReactiveSecurityContextHolder;
 import org.springframework.security.web.method.ResolvableMethod;
@@ -206,6 +209,38 @@ public class AuthenticationPrincipalArgumentResolverTests {
 		assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> argument.block());
 	}
 
+	@Test
+	public void resolveArgumentCustomMetaAnnotation() {
+		CustomUserPrincipal principal = new CustomUserPrincipal();
+		given(this.authentication.getPrincipal()).willReturn(principal);
+		Mono<Object> result = this.resolver
+			.resolveArgument(arg0("showUserCustomMetaAnnotation"), this.bindingContext, this.exchange)
+			.contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.authentication));
+		assertThat(result.block()).isEqualTo(principal.id);
+	}
+
+	@Test
+	public void resolveArgumentCustomMetaAnnotationTpl() {
+		CustomUserPrincipal principal = new CustomUserPrincipal();
+		given(this.authentication.getPrincipal()).willReturn(principal);
+		this.resolver.setTemplateDefaults(new AnnotationTemplateExpressionDefaults());
+		Mono<Object> result = this.resolver
+			.resolveArgument(arg0("showUserCustomMetaAnnotationTpl"), this.bindingContext, this.exchange)
+			.contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.authentication));
+		assertThat(result.block()).isEqualTo(principal.id);
+	}
+
+	private MethodParameter arg0(String methodName) {
+		ResolvableMethod method = ResolvableMethod.on(getClass()).named(methodName).build();
+		return new SynthesizingMethodParameter(method.method(), 0);
+	}
+
+	public void showUserCustomMetaAnnotation(@CurrentUser2(expression = "principal.id") int userId) {
+	}
+
+	public void showUserCustomMetaAnnotationTpl(@CurrentUser3(property = "id") int userId) {
+	}
+
 	void authenticationPrincipal(@AuthenticationPrincipal String principal,
 			@AuthenticationPrincipal Mono<String> monoPrincipal) {
 	}
@@ -278,4 +313,31 @@ public class AuthenticationPrincipalArgumentResolverTests {
 
 	}
 
+	static class CustomUserPrincipal {
+
+		public final int id = 1;
+
+		public Object getPrincipal() {
+			return this;
+		}
+
+	}
+
+	@Retention(RetentionPolicy.RUNTIME)
+	@AuthenticationPrincipal
+	public @interface CurrentUser2 {
+
+		@AliasFor(annotation = AuthenticationPrincipal.class)
+		String expression() default "";
+
+	}
+
+	@Retention(RetentionPolicy.RUNTIME)
+	@AuthenticationPrincipal(expression = "principal.{property}")
+	public @interface CurrentUser3 {
+
+		String property() default "";
+
+	}
+
 }