Improve @CurrentSecurityContext meta-annotations
Closes gh-15551
This commit is contained in:
+31
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,14 +17,18 @@
|
||||
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.annotation.AnnotationSynthesizer;
|
||||
import org.springframework.security.core.annotation.AnnotationSynthesizers;
|
||||
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
|
||||
import org.springframework.security.core.annotation.CurrentSecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
@@ -72,6 +76,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
* </p>
|
||||
*
|
||||
* @author Dan Zheng
|
||||
* @author DingHao
|
||||
* @since 5.2
|
||||
*/
|
||||
public final class CurrentSecurityContextArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
@@ -79,14 +84,19 @@ public final class CurrentSecurityContextArgumentResolver implements HandlerMeth
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
|
||||
.getContextHolderStrategy();
|
||||
|
||||
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
|
||||
|
||||
private ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private AnnotationSynthesizer<CurrentSecurityContext> synthesizer = AnnotationSynthesizers
|
||||
.requireUnique(CurrentSecurityContext.class);
|
||||
|
||||
private BeanResolver beanResolver;
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return SecurityContext.class.isAssignableFrom(parameter.getParameterType())
|
||||
|| findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
|
||||
|| findMethodAnnotation(parameter) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,7 +106,7 @@ public final class CurrentSecurityContextArgumentResolver implements HandlerMeth
|
||||
if (securityContext == null) {
|
||||
return null;
|
||||
}
|
||||
CurrentSecurityContext annotation = findMethodAnnotation(CurrentSecurityContext.class, parameter);
|
||||
CurrentSecurityContext annotation = findMethodAnnotation(parameter);
|
||||
if (annotation != null) {
|
||||
return resolveSecurityContextFromAnnotation(parameter, annotation, securityContext);
|
||||
}
|
||||
@@ -124,6 +134,19 @@ public final class CurrentSecurityContextArgumentResolver implements HandlerMeth
|
||||
this.beanResolver = beanResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure CurrentSecurityContext template resolution
|
||||
* <p>
|
||||
* By default, this value is <code>null</code>, which indicates that templates should
|
||||
* not be resolved.
|
||||
* @param templateDefaults - whether to resolve CurrentSecurityContext templates
|
||||
* parameters
|
||||
* @since 6.4
|
||||
*/
|
||||
public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
|
||||
this.synthesizer = AnnotationSynthesizers.requireUnique(CurrentSecurityContext.class, templateDefaults);
|
||||
}
|
||||
|
||||
private Object resolveSecurityContextFromAnnotation(MethodParameter parameter, CurrentSecurityContext annotation,
|
||||
SecurityContext securityContext) {
|
||||
Object securityContextResult = securityContext;
|
||||
@@ -149,24 +172,13 @@ public final class CurrentSecurityContextArgumentResolver implements HandlerMeth
|
||||
|
||||
/**
|
||||
* Obtain 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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+31
-20
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -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.CurrentSecurityContext;
|
||||
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 {@link SecurityContext}
|
||||
*
|
||||
* @author Dan Zheng
|
||||
* @author DingHao
|
||||
* @since 5.2
|
||||
*/
|
||||
public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumentResolverSupport {
|
||||
|
||||
private final Map<MethodParameter, Annotation> cachedAttributes = new ConcurrentHashMap<>();
|
||||
|
||||
private ExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private AnnotationSynthesizer<CurrentSecurityContext> synthesizer = AnnotationSynthesizers
|
||||
.requireUnique(CurrentSecurityContext.class);
|
||||
|
||||
private BeanResolver beanResolver;
|
||||
|
||||
public CurrentSecurityContextArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
|
||||
@@ -65,10 +75,22 @@ public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumen
|
||||
this.beanResolver = beanResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure CurrentSecurityContext template resolution
|
||||
* <p>
|
||||
* By default, this value is <code>null</code>, which indicates that templates should
|
||||
* not be resolved.
|
||||
* @param templateDefaults - whether to resolve CurrentSecurityContext templates
|
||||
* parameters
|
||||
* @since 6.4
|
||||
*/
|
||||
public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
|
||||
this.synthesizer = AnnotationSynthesizers.requireUnique(CurrentSecurityContext.class, templateDefaults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return isMonoSecurityContext(parameter)
|
||||
|| findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
|
||||
return isMonoSecurityContext(parameter) || findMethodAnnotation(parameter) != null;
|
||||
}
|
||||
|
||||
private boolean isMonoSecurityContext(MethodParameter parameter) {
|
||||
@@ -108,7 +130,7 @@ public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumen
|
||||
* @return the resolved object from expression.
|
||||
*/
|
||||
private Object resolveSecurityContext(MethodParameter parameter, SecurityContext securityContext) {
|
||||
CurrentSecurityContext annotation = findMethodAnnotation(CurrentSecurityContext.class, parameter);
|
||||
CurrentSecurityContext annotation = findMethodAnnotation(parameter);
|
||||
if (annotation != null) {
|
||||
return resolveSecurityContextFromAnnotation(annotation, parameter, securityContext);
|
||||
}
|
||||
@@ -162,24 +184,13 @@ public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumen
|
||||
|
||||
/**
|
||||
* 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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+55
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,10 +27,12 @@ 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.expression.spel.SpelEvaluationException;
|
||||
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.CurrentSecurityContext;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
@@ -247,6 +249,23 @@ public class CurrentSecurityContextArgumentResolverTests {
|
||||
.resolveArgument(showCurrentSecurityWithErrorOnInvalidTypeMisMatch(), null, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentCustomMetaAnnotation() {
|
||||
String principal = "current_authentcation";
|
||||
setAuthenticationPrincipal(principal);
|
||||
String p = (String) this.resolver.resolveArgument(showUserCustomMetaAnnotation(), null, null, null);
|
||||
assertThat(p).isEqualTo(principal);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentCustomMetaAnnotationTpl() {
|
||||
String principal = "current_authentcation";
|
||||
setAuthenticationPrincipal(principal);
|
||||
this.resolver.setTemplateDefaults(new AnnotationTemplateExpressionDefaults());
|
||||
String p = (String) this.resolver.resolveArgument(showUserCustomMetaAnnotationTpl(), null, null, null);
|
||||
assertThat(p).isEqualTo(principal);
|
||||
}
|
||||
|
||||
private MethodParameter showSecurityContextNoAnnotationTypeMismatch() {
|
||||
return getMethodParameter("showSecurityContextNoAnnotation", String.class);
|
||||
}
|
||||
@@ -307,6 +326,14 @@ public class CurrentSecurityContextArgumentResolverTests {
|
||||
return getMethodParameter("showCurrentAuthentication", Authentication.class);
|
||||
}
|
||||
|
||||
public MethodParameter showUserCustomMetaAnnotation() {
|
||||
return getMethodParameter("showUserCustomMetaAnnotation", String.class);
|
||||
}
|
||||
|
||||
public MethodParameter showUserCustomMetaAnnotationTpl() {
|
||||
return getMethodParameter("showUserCustomMetaAnnotationTpl", String.class);
|
||||
}
|
||||
|
||||
public MethodParameter showCurrentSecurityWithErrorOnInvalidType() {
|
||||
return getMethodParameter("showCurrentSecurityWithErrorOnInvalidType", SecurityContext.class);
|
||||
}
|
||||
@@ -394,6 +421,14 @@ public class CurrentSecurityContextArgumentResolverTests {
|
||||
public void showCurrentAuthentication(@CurrentAuthentication Authentication authentication) {
|
||||
}
|
||||
|
||||
public void showUserCustomMetaAnnotation(
|
||||
@AliasedCurrentSecurityContext(expression = "authentication.principal") String name) {
|
||||
}
|
||||
|
||||
public void showUserCustomMetaAnnotationTpl(
|
||||
@CurrentAuthenticationProperty(property = "principal") String name) {
|
||||
}
|
||||
|
||||
public void showCurrentSecurityWithErrorOnInvalidType(
|
||||
@CurrentSecurityWithErrorOnInvalidType SecurityContext context) {
|
||||
}
|
||||
@@ -447,4 +482,23 @@ public class CurrentSecurityContextArgumentResolverTests {
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.PARAMETER })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@CurrentSecurityContext
|
||||
@interface AliasedCurrentSecurityContext {
|
||||
|
||||
@AliasFor(annotation = CurrentSecurityContext.class)
|
||||
String expression() default "";
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.PARAMETER })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@CurrentSecurityContext(expression = "authentication.{property}")
|
||||
@interface CurrentAuthenticationProperty {
|
||||
|
||||
String property() default "";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+58
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,10 +31,12 @@ import reactor.util.context.Context;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
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.CurrentSecurityContext;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
@@ -402,6 +404,42 @@ public class CurrentSecurityContextArgumentResolverTests {
|
||||
ReactiveSecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentCustomMetaAnnotation() {
|
||||
MethodParameter parameter = ResolvableMethod.on(getClass())
|
||||
.named("showUserCustomMetaAnnotation")
|
||||
.build()
|
||||
.arg(Mono.class, String.class);
|
||||
Authentication auth = buildAuthenticationWithPrincipal("current_authentication");
|
||||
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
|
||||
Mono<Object> argument = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange);
|
||||
String principal = (String) argument.contextWrite(context).cast(Mono.class).block().block();
|
||||
assertThat(principal).isSameAs(auth.getPrincipal());
|
||||
ReactiveSecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentCustomMetaAnnotationTpl() {
|
||||
this.resolver.setTemplateDefaults(new AnnotationTemplateExpressionDefaults());
|
||||
MethodParameter parameter = ResolvableMethod.on(getClass())
|
||||
.named("showUserCustomMetaAnnotationTpl")
|
||||
.build()
|
||||
.arg(Mono.class, String.class);
|
||||
Authentication auth = buildAuthenticationWithPrincipal("current_authentication");
|
||||
Context context = ReactiveSecurityContextHolder.withAuthentication(auth);
|
||||
Mono<Object> argument = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange);
|
||||
String principal = (String) argument.contextWrite(context).cast(Mono.class).block().block();
|
||||
assertThat(principal).isSameAs(auth.getPrincipal());
|
||||
ReactiveSecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
void showUserCustomMetaAnnotation(
|
||||
@AliasedCurrentSecurityContext(expression = "authentication.principal") Mono<String> user) {
|
||||
}
|
||||
|
||||
void showUserCustomMetaAnnotationTpl(@CurrentAuthenticationProperty(property = "principal") Mono<String> user) {
|
||||
}
|
||||
|
||||
void securityContext(@CurrentSecurityContext Mono<SecurityContext> monoSecurityContext) {
|
||||
}
|
||||
|
||||
@@ -479,6 +517,25 @@ public class CurrentSecurityContextArgumentResolverTests {
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.PARAMETER })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@CurrentSecurityContext
|
||||
@interface AliasedCurrentSecurityContext {
|
||||
|
||||
@AliasFor(annotation = CurrentSecurityContext.class)
|
||||
String expression() default "";
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.PARAMETER })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@CurrentSecurityContext(expression = "authentication.{property}")
|
||||
@interface CurrentAuthenticationProperty {
|
||||
|
||||
String property() default "";
|
||||
|
||||
}
|
||||
|
||||
static class CustomSecurityContext implements SecurityContext {
|
||||
|
||||
private Authentication authentication;
|
||||
|
||||
Reference in New Issue
Block a user