Skip to content

Commit ec17bce

Browse files
committed
Improve @AuthenticationPrincipal meta-annotations
Closes gh-15286
1 parent e2f98db commit ec17bce

File tree

2 files changed

+84
-17
lines changed

2 files changed

+84
-17
lines changed

messaging/src/main/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolver.java

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,20 @@
1717
package org.springframework.security.messaging.context;
1818

1919
import java.lang.annotation.Annotation;
20+
import java.util.Map;
21+
import java.util.concurrent.ConcurrentHashMap;
2022

2123
import org.springframework.core.MethodParameter;
22-
import org.springframework.core.annotation.AnnotationUtils;
2324
import org.springframework.expression.Expression;
2425
import org.springframework.expression.ExpressionParser;
2526
import org.springframework.expression.spel.standard.SpelExpressionParser;
2627
import org.springframework.expression.spel.support.StandardEvaluationContext;
2728
import org.springframework.messaging.Message;
2829
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
2930
import org.springframework.security.core.Authentication;
31+
import org.springframework.security.core.annotation.AnnotationSynthesizer;
32+
import org.springframework.security.core.annotation.AnnotationSynthesizers;
33+
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
3034
import org.springframework.security.core.annotation.AuthenticationPrincipal;
3135
import org.springframework.security.core.context.SecurityContextHolder;
3236
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -83,18 +87,24 @@
8387
* </pre>
8488
*
8589
* @author Rob Winch
90+
* @author DingHao
8691
* @since 4.0
8792
*/
8893
public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
8994

9095
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
9196
.getContextHolderStrategy();
9297

98+
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
99+
93100
private ExpressionParser parser = new SpelExpressionParser();
94101

102+
private AnnotationSynthesizer<AuthenticationPrincipal> synthesizer = AnnotationSynthesizers
103+
.requireUnique(AuthenticationPrincipal.class);
104+
95105
@Override
96106
public boolean supportsParameter(MethodParameter parameter) {
97-
return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
107+
return findMethodAnnotation(parameter) != null;
98108
}
99109

100110
@Override
@@ -104,7 +114,7 @@ public Object resolveArgument(MethodParameter parameter, Message<?> message) {
104114
return null;
105115
}
106116
Object principal = authentication.getPrincipal();
107-
AuthenticationPrincipal authPrincipal = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
117+
AuthenticationPrincipal authPrincipal = findMethodAnnotation(parameter);
108118
String expressionToParse = authPrincipal.expression();
109119
if (StringUtils.hasLength(expressionToParse)) {
110120
StandardEvaluationContext context = new StandardEvaluationContext();
@@ -133,26 +143,29 @@ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy secur
133143
this.securityContextHolderStrategy = securityContextHolderStrategy;
134144
}
135145

146+
/**
147+
* Configure AuthenticationPrincipal template resolution
148+
* <p>
149+
* By default, this value is <code>null</code>, which indicates that templates should
150+
* not be resolved.
151+
* @param templateDefaults - whether to resolve AuthenticationPrincipal templates
152+
* parameters
153+
* @since 6.4
154+
*/
155+
public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
156+
this.synthesizer = AnnotationSynthesizers.requireUnique(AuthenticationPrincipal.class, templateDefaults);
157+
}
158+
136159
/**
137160
* Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
138-
* @param annotationClass the class of the {@link Annotation} to find on the
139161
* {@link MethodParameter}
140162
* @param parameter the {@link MethodParameter} to search for an {@link Annotation}
141163
* @return the {@link Annotation} that was found or null.
142164
*/
143-
private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
144-
T annotation = parameter.getParameterAnnotation(annotationClass);
145-
if (annotation != null) {
146-
return annotation;
147-
}
148-
Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
149-
for (Annotation toSearch : annotationsToSearch) {
150-
annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
151-
if (annotation != null) {
152-
return annotation;
153-
}
154-
}
155-
return null;
165+
@SuppressWarnings("unchecked")
166+
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
167+
return (T) this.cachedAttributes.computeIfAbsent(parameter,
168+
(methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter()));
156169
}
157170

158171
}

messaging/src/test/java/org/springframework/security/messaging/context/AuthenticationPrincipalArgumentResolverTests.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
import org.junit.jupiter.api.Test;
2828

2929
import org.springframework.core.MethodParameter;
30+
import org.springframework.core.annotation.AliasFor;
3031
import org.springframework.security.authentication.TestingAuthenticationToken;
32+
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
3133
import org.springframework.security.core.annotation.AuthenticationPrincipal;
3234
import org.springframework.security.core.authority.AuthorityUtils;
3335
import org.springframework.security.core.context.SecurityContextHolder;
@@ -167,6 +169,23 @@ public void resolveArgumentObject() throws Exception {
167169
assertThat(this.resolver.resolveArgument(showUserAnnotationObject(), null)).isEqualTo(this.expectedPrincipal);
168170
}
169171

172+
@Test
173+
public void resolveArgumentCustomMetaAnnotation() throws Exception {
174+
CustomUserPrincipal principal = new CustomUserPrincipal();
175+
setAuthenticationPrincipal(principal);
176+
this.expectedPrincipal = principal.id;
177+
assertThat(this.resolver.resolveArgument(showUserCustomMetaAnnotation(), null)).isEqualTo(principal.id);
178+
}
179+
180+
@Test
181+
public void resolveArgumentCustomMetaAnnotationTpl() throws Exception {
182+
CustomUserPrincipal principal = new CustomUserPrincipal();
183+
setAuthenticationPrincipal(principal);
184+
this.resolver.setTemplateDefaults(new AnnotationTemplateExpressionDefaults());
185+
this.expectedPrincipal = principal.id;
186+
assertThat(this.resolver.resolveArgument(showUserCustomMetaAnnotationTpl(), null)).isEqualTo(principal.id);
187+
}
188+
170189
private MethodParameter showUserNoAnnotation() {
171190
return getMethodParameter("showUserNoAnnotation", String.class);
172191
}
@@ -195,6 +214,14 @@ private MethodParameter showUserCustomAnnotation() {
195214
return getMethodParameter("showUserCustomAnnotation", CustomUserPrincipal.class);
196215
}
197216

217+
private MethodParameter showUserCustomMetaAnnotation() {
218+
return getMethodParameter("showUserCustomMetaAnnotation", int.class);
219+
}
220+
221+
private MethodParameter showUserCustomMetaAnnotationTpl() {
222+
return getMethodParameter("showUserCustomMetaAnnotationTpl", int.class);
223+
}
224+
198225
private MethodParameter showUserSpel() {
199226
return getMethodParameter("showUserSpel", String.class);
200227
}
@@ -236,6 +263,23 @@ private void setAuthenticationPrincipal(Object principal) {
236263

237264
}
238265

266+
@Retention(RetentionPolicy.RUNTIME)
267+
@AuthenticationPrincipal
268+
public @interface CurrentUser2 {
269+
270+
@AliasFor(annotation = AuthenticationPrincipal.class)
271+
String expression() default "";
272+
273+
}
274+
275+
@Retention(RetentionPolicy.RUNTIME)
276+
@AuthenticationPrincipal(expression = "principal.{property}")
277+
public @interface CurrentUser3 {
278+
279+
String property() default "";
280+
281+
}
282+
239283
public static class TestController {
240284

241285
public void showUserNoAnnotation(String user) {
@@ -260,6 +304,12 @@ public void showUserAnnotation(@AuthenticationPrincipal CustomUserPrincipal user
260304
public void showUserCustomAnnotation(@CurrentUser CustomUserPrincipal user) {
261305
}
262306

307+
public void showUserCustomMetaAnnotation(@CurrentUser2(expression = "principal.id") int userId) {
308+
}
309+
310+
public void showUserCustomMetaAnnotationTpl(@CurrentUser3(property = "id") int userId) {
311+
}
312+
263313
public void showUserAnnotation(@AuthenticationPrincipal Object user) {
264314
}
265315

@@ -281,6 +331,10 @@ static class CustomUserPrincipal {
281331

282332
public final int id = 1;
283333

334+
public Object getPrincipal() {
335+
return this;
336+
}
337+
284338
}
285339

286340
public static class CopyUserPrincipal {

0 commit comments

Comments
 (0)