Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve @AuthenticationPrincipal meta-annotations
Browse files Browse the repository at this point in the history
Closes gh-15286
kse-music committed Jul 23, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent e2f98db commit 25edd68
Showing 8 changed files with 340 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -17,16 +17,20 @@
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;
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,18 +87,24 @@
* </pre>
*
* @author Rob Winch
* @author DingHao
* @since 4.0
*/
public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {

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 Object resolveArgument(MethodParameter parameter, Message<?> message) {
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 void setSecurityContextHolderStrategy(SecurityContextHolderStrategy secur
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()));
}

}
Original file line number Diff line number Diff line change
@@ -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.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.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 @@
* </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 void setAdapterRegistry(ReactiveAdapterRegistry adapterRegistry) {

@Override
public boolean supportsParameter(MethodParameter parameter) {
return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
return findMethodAnnotation(parameter) != null;
}

@Override
@@ -138,7 +148,7 @@ public Mono<Object> resolveArgument(MethodParameter parameter, Message<?> messag
}

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 @@ private boolean isInvalidType(MethodParameter parameter, Object principal) {
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()));
}

}
Original file line number Diff line number Diff line change
@@ -27,7 +27,9 @@
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 void resolveArgumentObject() throws Exception {
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 @@ private MethodParameter showUserCustomAnnotation() {
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 @@ private void setAuthenticationPrincipal(Object principal) {

}

@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 void showUserAnnotation(@AuthenticationPrincipal CustomUserPrincipal user
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 @@ static class CustomUserPrincipal {

public final int id = 1;

public Object getPrincipal() {
return this;
}

}

public static class CopyUserPrincipal {
Original file line number Diff line number Diff line change
@@ -23,10 +23,12 @@
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 @@ private MethodParameter arg0(String methodName) {

}

@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 "";

}

}
Original file line number Diff line number Diff line change
@@ -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,20 +90,26 @@
* </pre>
*
* @author Rob Winch
* @author DingHao
* @since 4.0
*/
public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {

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 Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
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 void setSecurityContextHolderStrategy(SecurityContextHolderStrategy secur
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()));
}

}
Original file line number Diff line number Diff line change
@@ -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.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 @@
* 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 void setBeanResolver(BeanResolver beanResolver) {

@Override
public boolean supportsParameter(MethodParameter parameter) {
return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
return findMethodAnnotation(parameter) != null;
}

@Override
@@ -82,7 +92,7 @@ public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bi
}

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 @@ private boolean isInvalidType(MethodParameter parameter, Object principal) {
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()));
}

}
Original file line number Diff line number Diff line change
@@ -27,8 +27,10 @@
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 void resolveArgumentObject() throws Exception {
.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 @@ private MethodParameter showUserAnnotationObject() {
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 @@ private void setAuthenticationPrincipal(Object principal) {

}

@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 void showUserAnnotation(@AuthenticationPrincipal CustomUserPrincipal user
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 @@ static class CustomUserPrincipal {

public final int id = 1;

public Object getPrincipal() {
return this;
}

}

public static class CopyUserPrincipal {
Original file line number Diff line number Diff line change
@@ -31,8 +31,11 @@

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 void resolveArgumentWhenErrorOnInvalidTypeExplicitTrue() {
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 int getId() {

}

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 "";

}

}

0 comments on commit 25edd68

Please sign in to comment.