diff --git a/messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java b/messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java index 8cd93d095d..e35bc82ff3 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java +++ b/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; * * * @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 cachedAttributes = new ConcurrentHashMap<>(); + private ExpressionParser parser = new SpelExpressionParser(); + private AnnotationSynthesizer 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 + *

+ * By default, this value is null, 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 findMethodAnnotation(Class 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 findMethodAnnotation(MethodParameter parameter) { + return (T) this.cachedAttributes.computeIfAbsent(parameter, + (methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter())); } } diff --git a/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java b/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java index f6baa3a328..e92d589e3c 100644 --- a/messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolver.java +++ b/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; * * * @author Rob Winch + * @author DingHao * @since 5.2 */ public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver { + private final Map cachedAttributes = new ConcurrentHashMap<>(); + private ExpressionParser parser = new SpelExpressionParser(); + private AnnotationSynthesizer 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 + *

+ * By default, this value is null, 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 findMethodAnnotation(Class 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 findMethodAnnotation(MethodParameter parameter) { + return (T) this.cachedAttributes.computeIfAbsent(parameter, + (methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter())); } } diff --git a/messaging/src/test/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolverTests.java b/messaging/src/test/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolverTests.java index acd41aba8e..ce1437f0b0 100644 --- a/messaging/src/test/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolverTests.java +++ b/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 { diff --git a/messaging/src/test/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolverTests.java b/messaging/src/test/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolverTests.java index 04b6686920..bf66448af1 100644 --- a/messaging/src/test/java/org/springframework/security/messaging/handler/invocation/reactive/AuthenticationPrincipalArgumentResolverTests.java +++ b/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 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 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 ""; + } } diff --git a/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java b/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java index aa5db12100..4056c3d484 100644 --- a/web/src/main/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolver.java +++ b/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; * * * @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 cachedAttributes = new ConcurrentHashMap<>(); + private ExpressionParser parser = new SpelExpressionParser(); + private AnnotationSynthesizer 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 + *

+ * By default, this value is null, 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 findMethodAnnotation(Class 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 findMethodAnnotation(MethodParameter parameter) { + return (T) this.cachedAttributes.computeIfAbsent(parameter, + (methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter())); } } diff --git a/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java b/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java index 1e2d3d5556..6d46223199 100644 --- a/web/src/main/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolver.java +++ b/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 cachedAttributes = new ConcurrentHashMap<>(); + private ExpressionParser parser = new SpelExpressionParser(); + private AnnotationSynthesizer 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 + *

+ * By default, this value is null, 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 findMethodAnnotation(Class 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 findMethodAnnotation(MethodParameter parameter) { + return (T) this.cachedAttributes.computeIfAbsent(parameter, + (methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter())); } } diff --git a/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java b/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java index 6b80024df5..b4542230a4 100644 --- a/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java +++ b/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 { diff --git a/web/src/test/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolverTests.java b/web/src/test/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolverTests.java index 3c370ffa73..59bf28be85 100644 --- a/web/src/test/java/org/springframework/security/web/reactive/result/method/annotation/AuthenticationPrincipalArgumentResolverTests.java +++ b/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 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 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 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 ""; + + } + }