mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-30 16:52:13 +00:00
Improve @AuthenticationPrincipal meta-annotations
Closes gh-15286
This commit is contained in:
parent
6d657ea3da
commit
9aaf959400
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 "";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 "";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user