Skip to content

Commit 848c5c6

Browse files
committed
Improve @CurrentSecurityContext meta-annotations
Closes gh-15551
1 parent 08b8b09 commit 848c5c6

File tree

5 files changed

+172
-39
lines changed

5 files changed

+172
-39
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentRes
9898
CurrentSecurityContextArgumentResolver currentSecurityContextArgumentResolver = new CurrentSecurityContextArgumentResolver();
9999
currentSecurityContextArgumentResolver.setBeanResolver(this.beanResolver);
100100
currentSecurityContextArgumentResolver.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
101+
currentSecurityContextArgumentResolver.setTemplateDefaults(this.templateDefaults);
101102
argumentResolvers.add(currentSecurityContextArgumentResolver);
102103
argumentResolvers.add(new CsrfTokenArgumentResolver());
103104
}

messaging/src/main/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolver.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.security.messaging.handler.invocation.reactive;
1818

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

2123
import org.reactivestreams.Publisher;
2224
import reactor.core.publisher.Mono;
@@ -25,7 +27,6 @@
2527
import org.springframework.core.ReactiveAdapter;
2628
import org.springframework.core.ReactiveAdapterRegistry;
2729
import org.springframework.core.ResolvableType;
28-
import org.springframework.core.annotation.AnnotationUtils;
2930
import org.springframework.expression.BeanResolver;
3031
import org.springframework.expression.Expression;
3132
import org.springframework.expression.ExpressionParser;
@@ -34,6 +35,9 @@
3435
import org.springframework.messaging.Message;
3536
import org.springframework.messaging.handler.invocation.reactive.HandlerMethodArgumentResolver;
3637
import org.springframework.security.core.Authentication;
38+
import org.springframework.security.core.annotation.AnnotationSynthesizer;
39+
import org.springframework.security.core.annotation.AnnotationSynthesizers;
40+
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
3741
import org.springframework.security.core.annotation.CurrentSecurityContext;
3842
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
3943
import org.springframework.security.core.context.SecurityContext;
@@ -88,12 +92,18 @@
8892
* </pre>
8993
*
9094
* @author Rob Winch
95+
* @author DingHao
9196
* @since 5.2
9297
*/
9398
public class CurrentSecurityContextArgumentResolver implements HandlerMethodArgumentResolver {
9499

100+
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
101+
95102
private ExpressionParser parser = new SpelExpressionParser();
96103

104+
private AnnotationSynthesizer<CurrentSecurityContext> synthesizer = AnnotationSynthesizers
105+
.requireUnique(CurrentSecurityContext.class);
106+
97107
private BeanResolver beanResolver;
98108

99109
private ReactiveAdapterRegistry adapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
@@ -118,8 +128,7 @@ public void setAdapterRegistry(ReactiveAdapterRegistry adapterRegistry) {
118128

119129
@Override
120130
public boolean supportsParameter(MethodParameter parameter) {
121-
return isMonoSecurityContext(parameter)
122-
|| findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
131+
return isMonoSecurityContext(parameter) || findMethodAnnotation(parameter) != null;
123132
}
124133

125134
private boolean isMonoSecurityContext(MethodParameter parameter) {
@@ -149,7 +158,7 @@ public Mono<Object> resolveArgument(MethodParameter parameter, Message<?> messag
149158
}
150159

151160
private Object resolveSecurityContext(MethodParameter parameter, Object securityContext) {
152-
CurrentSecurityContext contextAnno = findMethodAnnotation(CurrentSecurityContext.class, parameter);
161+
CurrentSecurityContext contextAnno = findMethodAnnotation(parameter);
153162
if (contextAnno != null) {
154163
return resolveSecurityContextFromAnnotation(contextAnno, parameter, securityContext);
155164
}
@@ -193,26 +202,28 @@ private boolean isInvalidType(MethodParameter parameter, Object value) {
193202
return !typeToCheck.isAssignableFrom(value.getClass());
194203
}
195204

205+
/**
206+
* Configure CurrentSecurityContext template resolution
207+
* <p>
208+
* By default, this value is <code>null</code>, which indicates that templates should
209+
* not be resolved.
210+
* @param templateDefaults - whether to resolve CurrentSecurityContext templates
211+
* parameters
212+
* @since 6.4
213+
*/
214+
public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
215+
this.synthesizer = AnnotationSynthesizers.requireUnique(CurrentSecurityContext.class, templateDefaults);
216+
}
217+
196218
/**
197219
* Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
198-
* @param annotationClass the class of the {@link Annotation} to find on the
199-
* {@link MethodParameter}
200220
* @param parameter the {@link MethodParameter} to search for an {@link Annotation}
201221
* @return the {@link Annotation} that was found or null.
202222
*/
203-
private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
204-
T annotation = parameter.getParameterAnnotation(annotationClass);
205-
if (annotation != null) {
206-
return annotation;
207-
}
208-
Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
209-
for (Annotation toSearch : annotationsToSearch) {
210-
annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
211-
if (annotation != null) {
212-
return annotation;
213-
}
214-
}
215-
return null;
223+
@SuppressWarnings("unchecked")
224+
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
225+
return (T) this.cachedAttributes.computeIfAbsent(parameter,
226+
(methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter()));
216227
}
217228

218229
}

messaging/src/test/java/org/springframework/security/messaging/handler/invocation/reactive/CurrentSecurityContextArgumentResolverTests.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@
1616

1717
package org.springframework.security.messaging.handler.invocation.reactive;
1818

19+
import java.lang.annotation.ElementType;
1920
import java.lang.annotation.Retention;
2021
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
2123

2224
import org.junit.jupiter.api.Test;
2325
import reactor.core.publisher.Mono;
2426

2527
import org.springframework.core.MethodParameter;
28+
import org.springframework.core.annotation.AliasFor;
2629
import org.springframework.core.annotation.SynthesizingMethodParameter;
2730
import org.springframework.security.authentication.TestAuthentication;
2831
import org.springframework.security.core.Authentication;
32+
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
2933
import org.springframework.security.core.annotation.CurrentSecurityContext;
3034
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
3135
import org.springframework.security.core.context.SecurityContext;
@@ -171,6 +175,39 @@ public void resolveArgumentWhenMonoCustomSecurityContextNoAnnotationThenFound()
171175
assertThat(result.block().getAuthentication().getPrincipal()).isEqualTo(authentication.getPrincipal());
172176
}
173177

178+
@Test
179+
public void resolveArgumentCustomMetaAnnotation() {
180+
Authentication authentication = TestAuthentication.authenticatedUser();
181+
CustomSecurityContext securityContext = new CustomSecurityContext();
182+
securityContext.setAuthentication(authentication);
183+
Mono<UserDetails> result = (Mono<UserDetails>) this.resolver
184+
.resolveArgument(arg0("showUserCustomMetaAnnotation"), null)
185+
.contextWrite(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)))
186+
.block();
187+
assertThat(result.block()).isEqualTo(authentication.getPrincipal());
188+
}
189+
190+
@Test
191+
public void resolveArgumentCustomMetaAnnotationTpl() {
192+
this.resolver.setTemplateDefaults(new AnnotationTemplateExpressionDefaults());
193+
Authentication authentication = TestAuthentication.authenticatedUser();
194+
CustomSecurityContext securityContext = new CustomSecurityContext();
195+
securityContext.setAuthentication(authentication);
196+
Mono<UserDetails> result = (Mono<UserDetails>) this.resolver
197+
.resolveArgument(arg0("showUserCustomMetaAnnotationTpl"), null)
198+
.contextWrite(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)))
199+
.block();
200+
assertThat(result.block()).isEqualTo(authentication.getPrincipal());
201+
}
202+
203+
private void showUserCustomMetaAnnotation(
204+
@CurrentAuthentication2(expression = "authentication.principal") Mono<UserDetails> user) {
205+
}
206+
207+
private void showUserCustomMetaAnnotationTpl(
208+
@CurrentAuthentication3(property = "principal") Mono<UserDetails> user) {
209+
}
210+
174211
@SuppressWarnings("unused")
175212
private void monoCustomSecurityContext(Mono<CustomSecurityContext> securityContext) {
176213
}
@@ -186,6 +223,25 @@ private MethodParameter arg0(String methodName) {
186223

187224
}
188225

226+
@Target({ ElementType.PARAMETER })
227+
@Retention(RetentionPolicy.RUNTIME)
228+
@CurrentSecurityContext
229+
@interface CurrentAuthentication2 {
230+
231+
@AliasFor(annotation = CurrentSecurityContext.class)
232+
String expression() default "";
233+
234+
}
235+
236+
@Target({ ElementType.PARAMETER })
237+
@Retention(RetentionPolicy.RUNTIME)
238+
@CurrentSecurityContext(expression = "authentication.{property}")
239+
@interface CurrentAuthentication3 {
240+
241+
String property() default "";
242+
243+
}
244+
189245
static class CustomSecurityContext implements SecurityContext {
190246

191247
private Authentication authentication;

web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,14 +17,18 @@
1717
package org.springframework.security.web.method.annotation;
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.BeanResolver;
2425
import org.springframework.expression.Expression;
2526
import org.springframework.expression.ExpressionParser;
2627
import org.springframework.expression.spel.standard.SpelExpressionParser;
2728
import org.springframework.expression.spel.support.StandardEvaluationContext;
29+
import org.springframework.security.core.annotation.AnnotationSynthesizer;
30+
import org.springframework.security.core.annotation.AnnotationSynthesizers;
31+
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
2832
import org.springframework.security.core.annotation.CurrentSecurityContext;
2933
import org.springframework.security.core.context.SecurityContext;
3034
import org.springframework.security.core.context.SecurityContextHolder;
@@ -72,21 +76,27 @@
7276
* </p>
7377
*
7478
* @author Dan Zheng
79+
* @author DingHao
7580
* @since 5.2
7681
*/
7782
public final class CurrentSecurityContextArgumentResolver implements HandlerMethodArgumentResolver {
7883

7984
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
8085
.getContextHolderStrategy();
8186

87+
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
88+
8289
private ExpressionParser parser = new SpelExpressionParser();
8390

91+
private AnnotationSynthesizer<CurrentSecurityContext> synthesizer = AnnotationSynthesizers
92+
.requireUnique(CurrentSecurityContext.class);
93+
8494
private BeanResolver beanResolver;
8595

8696
@Override
8797
public boolean supportsParameter(MethodParameter parameter) {
8898
return SecurityContext.class.isAssignableFrom(parameter.getParameterType())
89-
|| findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
99+
|| findMethodAnnotation(parameter) != null;
90100
}
91101

92102
@Override
@@ -96,7 +106,7 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
96106
if (securityContext == null) {
97107
return null;
98108
}
99-
CurrentSecurityContext annotation = findMethodAnnotation(CurrentSecurityContext.class, parameter);
109+
CurrentSecurityContext annotation = findMethodAnnotation(parameter);
100110
if (annotation != null) {
101111
return resolveSecurityContextFromAnnotation(parameter, annotation, securityContext);
102112
}
@@ -124,6 +134,19 @@ public void setBeanResolver(BeanResolver beanResolver) {
124134
this.beanResolver = beanResolver;
125135
}
126136

137+
/**
138+
* Configure CurrentSecurityContext template resolution
139+
* <p>
140+
* By default, this value is <code>null</code>, which indicates that templates should
141+
* not be resolved.
142+
* @param templateDefaults - whether to resolve CurrentSecurityContext templates
143+
* parameters
144+
* @since 6.4
145+
*/
146+
public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
147+
this.synthesizer = AnnotationSynthesizers.requireUnique(CurrentSecurityContext.class, templateDefaults);
148+
}
149+
127150
private Object resolveSecurityContextFromAnnotation(MethodParameter parameter, CurrentSecurityContext annotation,
128151
SecurityContext securityContext) {
129152
Object securityContextResult = securityContext;
@@ -149,24 +172,13 @@ private Object resolveSecurityContextFromAnnotation(MethodParameter parameter, C
149172

150173
/**
151174
* Obtain the specified {@link Annotation} on the specified {@link MethodParameter}.
152-
* @param annotationClass the class of the {@link Annotation} to find on the
153-
* {@link MethodParameter}
154175
* @param parameter the {@link MethodParameter} to search for an {@link Annotation}
155176
* @return the {@link Annotation} that was found or null.
156177
*/
157-
private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
158-
T annotation = parameter.getParameterAnnotation(annotationClass);
159-
if (annotation != null) {
160-
return annotation;
161-
}
162-
Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
163-
for (Annotation toSearch : annotationsToSearch) {
164-
annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
165-
if (annotation != null) {
166-
return annotation;
167-
}
168-
}
169-
return null;
178+
@SuppressWarnings("unchecked")
179+
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) {
180+
return (T) this.cachedAttributes.computeIfAbsent(parameter,
181+
(methodParameter) -> this.synthesizer.synthesize(methodParameter.getParameter()));
170182
}
171183

172184
}

0 commit comments

Comments
 (0)