Added Testing

Issue gh-16177
This commit is contained in:
Josh Cummings 2024-12-09 17:54:05 -07:00
parent f565b23b51
commit 4cbaabb239
6 changed files with 203 additions and 9 deletions

View File

@ -16,6 +16,10 @@
package org.springframework.security.config.annotation.web.reactive; package org.springframework.security.config.annotation.web.reactive;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -28,6 +32,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.core.io.buffer.DefaultDataBufferFactory;
@ -404,11 +409,28 @@ public class EnableWebFluxSecurityTests {
} }
@RestController @Target({ ElementType.PARAMETER })
static class AuthenticationPrincipalResolver { @Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal
@interface Property {
@AliasFor(attribute = "expression", annotation = AuthenticationPrincipal.class)
String value() default "id";
}
interface UsernameResolver {
String username(@Property("@principalBean.username(#this)") String username);
}
@RestController
static class AuthenticationPrincipalResolver implements UsernameResolver {
@Override
@GetMapping("/spel") @GetMapping("/spel")
String username(@AuthenticationPrincipal(expression = "@principalBean.username(#this)") String username) { public String username(String username) {
return username; return username;
} }

View File

@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotatedMethod;
import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
@ -186,10 +187,21 @@ public class AuthenticationPrincipalArgumentResolverTests {
assertThat(this.resolver.resolveArgument(showUserCustomMetaAnnotationTpl(), null)).isEqualTo(principal.id); assertThat(this.resolver.resolveArgument(showUserCustomMetaAnnotationTpl(), null)).isEqualTo(principal.id);
} }
@Test
public void resolveArgumentWhenAliasForOnInterfaceThenInherits() {
CustomUserPrincipal principal = new CustomUserPrincipal();
setAuthenticationPrincipal(principal);
assertThat(this.resolver.resolveArgument(showUserNoConcreteAnnotation(), null)).isEqualTo(principal.property);
}
private MethodParameter showUserNoAnnotation() { private MethodParameter showUserNoAnnotation() {
return getMethodParameter("showUserNoAnnotation", String.class); return getMethodParameter("showUserNoAnnotation", String.class);
} }
private MethodParameter showUserNoConcreteAnnotation() {
return getMethodParameter("showUserNoConcreteAnnotation", String.class);
}
private MethodParameter showUserAnnotationString() { private MethodParameter showUserAnnotationString() {
return getMethodParameter("showUserAnnotation", String.class); return getMethodParameter("showUserAnnotation", String.class);
} }
@ -240,7 +252,7 @@ public class AuthenticationPrincipalArgumentResolverTests {
private MethodParameter getMethodParameter(String methodName, Class<?>... paramTypes) { private MethodParameter getMethodParameter(String methodName, Class<?>... paramTypes) {
Method method = ReflectionUtils.findMethod(TestController.class, methodName, paramTypes); Method method = ReflectionUtils.findMethod(TestController.class, methodName, paramTypes);
return new MethodParameter(method, 0); return new AnnotatedMethod(method).getMethodParameters()[0];
} }
private void setAuthenticationPrincipal(Object principal) { private void setAuthenticationPrincipal(Object principal) {
@ -280,11 +292,32 @@ public class AuthenticationPrincipalArgumentResolverTests {
} }
public static class TestController { @Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal
@interface Property {
@AliasFor(attribute = "expression", annotation = AuthenticationPrincipal.class)
String value() default "id";
}
private interface TestInterface {
void showUserNoConcreteAnnotation(@Property("property") String property);
}
public static class TestController implements TestInterface {
public void showUserNoAnnotation(String user) { public void showUserNoAnnotation(String user) {
} }
@Override
public void showUserNoConcreteAnnotation(String user) {
}
public void showUserAnnotation(@AuthenticationPrincipal String user) { public void showUserAnnotation(@AuthenticationPrincipal String user) {
} }

View File

@ -16,14 +16,17 @@
package org.springframework.security.messaging.handler.invocation.reactive; package org.springframework.security.messaging.handler.invocation.reactive;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotatedMethod;
import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.security.authentication.TestAuthentication; import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken;
@ -128,6 +131,19 @@ public class AuthenticationPrincipalArgumentResolverTests {
assertThat(this.resolver.supportsParameter(arg0("monoUserDetails"))).isFalse(); assertThat(this.resolver.supportsParameter(arg0("monoUserDetails"))).isFalse();
} }
@Test
public void resolveArgumentWhenAliasForOnInterfaceThenInherits() {
CustomUserPrincipal principal = new CustomUserPrincipal();
Authentication authentication = new TestingAuthenticationToken(principal, "password", "ROLE_USER");
ResolvableMethod method = ResolvableMethod.on(TestController.class)
.named("showUserNoConcreteAnnotation")
.method();
MethodParameter parameter = new AnnotatedMethod(method.method()).getMethodParameters()[0];
Mono<Object> result = this.resolver.resolveArgument(parameter, null)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));
assertThat(result.block()).isEqualTo(principal.property);
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
private void monoUserDetails(Mono<UserDetails> user) { private void monoUserDetails(Mono<UserDetails> user) {
} }
@ -172,6 +188,8 @@ public class AuthenticationPrincipalArgumentResolverTests {
public final int id = 1; public final int id = 1;
public final String property = "property";
public Object getPrincipal() { public Object getPrincipal() {
return this; return this;
} }
@ -195,4 +213,29 @@ public class AuthenticationPrincipalArgumentResolverTests {
} }
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal
@interface Property {
@AliasFor(attribute = "expression", annotation = AuthenticationPrincipal.class)
String value() default "id";
}
private interface TestInterface {
void showUserNoConcreteAnnotation(@Property("property") String property);
}
private static class TestController implements TestInterface {
@Override
public void showUserNoConcreteAnnotation(String user) {
}
}
} }

View File

@ -19,6 +19,8 @@ package org.springframework.security.web.method.annotation;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.expression.BeanResolver; import org.springframework.expression.BeanResolver;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser; import org.springframework.expression.ExpressionParser;
@ -93,6 +95,8 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*/ */
public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver { public final class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
private final Class<AuthenticationPrincipal> annotationType = AuthenticationPrincipal.class;
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
.getContextHolderStrategy(); .getContextHolderStrategy();
@ -101,6 +105,8 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
private SecurityAnnotationScanner<AuthenticationPrincipal> scanner = SecurityAnnotationScanners private SecurityAnnotationScanner<AuthenticationPrincipal> scanner = SecurityAnnotationScanners
.requireUnique(AuthenticationPrincipal.class); .requireUnique(AuthenticationPrincipal.class);
private boolean useAnnotationTemplate = false;
private BeanResolver beanResolver; private BeanResolver beanResolver;
@Override @Override
@ -165,6 +171,7 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
*/ */
public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) { public void setTemplateDefaults(AnnotationTemplateExpressionDefaults templateDefaults) {
this.scanner = SecurityAnnotationScanners.requireUnique(AuthenticationPrincipal.class, templateDefaults); this.scanner = SecurityAnnotationScanners.requireUnique(AuthenticationPrincipal.class, templateDefaults);
this.useAnnotationTemplate = templateDefaults != null;
} }
/** /**
@ -174,8 +181,22 @@ public final class AuthenticationPrincipalArgumentResolver implements HandlerMet
* @return the {@link Annotation} that was found or null. * @return the {@link Annotation} that was found or null.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T extends Annotation> T findMethodAnnotation(MethodParameter parameter) { private AuthenticationPrincipal findMethodAnnotation(MethodParameter parameter) {
return (T) this.scanner.scan(parameter.getParameter()); if (this.useAnnotationTemplate) {
return this.scanner.scan(parameter.getParameter());
}
AuthenticationPrincipal annotation = parameter.getParameterAnnotation(this.annotationType);
if (annotation != null) {
return annotation;
}
Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
for (Annotation toSearch : annotationsToSearch) {
annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), this.annotationType);
if (annotation != null) {
return MergedAnnotations.from(toSearch).get(this.annotationType).synthesize();
}
}
return null;
} }
} }

View File

@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotatedMethod;
import org.springframework.expression.BeanResolver; import org.springframework.expression.BeanResolver;
import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
@ -214,10 +215,22 @@ public class AuthenticationPrincipalArgumentResolverTests {
.isEqualTo(this.expectedPrincipal); .isEqualTo(this.expectedPrincipal);
} }
@Test
public void resolveArgumentWhenAliasForOnInterfaceThenInherits() throws Exception {
CustomUserPrincipal principal = new CustomUserPrincipal();
setAuthenticationPrincipal(principal);
assertThat(this.resolver.resolveArgument(showUserNoConcreteAnnotation(), null, null, null))
.isEqualTo(principal.property);
}
private MethodParameter showUserNoAnnotation() { private MethodParameter showUserNoAnnotation() {
return getMethodParameter("showUserNoAnnotation", String.class); return getMethodParameter("showUserNoAnnotation", String.class);
} }
private MethodParameter showUserNoConcreteAnnotation() {
return getMethodParameter("showUserNoConcreteAnnotation", String.class);
}
private MethodParameter showUserAnnotationString() { private MethodParameter showUserAnnotationString() {
return getMethodParameter("showUserAnnotation", String.class); return getMethodParameter("showUserAnnotation", String.class);
} }
@ -272,7 +285,7 @@ public class AuthenticationPrincipalArgumentResolverTests {
private MethodParameter getMethodParameter(String methodName, Class<?>... paramTypes) { private MethodParameter getMethodParameter(String methodName, Class<?>... paramTypes) {
Method method = ReflectionUtils.findMethod(TestController.class, methodName, paramTypes); Method method = ReflectionUtils.findMethod(TestController.class, methodName, paramTypes);
return new MethodParameter(method, 0); return new AnnotatedMethod(method).getMethodParameters()[0];
} }
private void setAuthenticationPrincipal(Object principal) { private void setAuthenticationPrincipal(Object principal) {
@ -295,6 +308,16 @@ public class AuthenticationPrincipalArgumentResolverTests {
} }
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal
@interface Property {
@AliasFor(attribute = "expression", annotation = AuthenticationPrincipal.class)
String value() default "id";
}
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal @AuthenticationPrincipal
public @interface CurrentUser2 { public @interface CurrentUser2 {
@ -312,11 +335,22 @@ public class AuthenticationPrincipalArgumentResolverTests {
} }
public static class TestController { public interface TestInterface {
void showUserNoConcreteAnnotation(@Property("property") String property);
}
public static class TestController implements TestInterface {
public void showUserNoAnnotation(String user) { public void showUserNoAnnotation(String user) {
} }
@Override
public void showUserNoConcreteAnnotation(String user) {
}
public void showUserAnnotation(@AuthenticationPrincipal String user) { public void showUserAnnotation(@AuthenticationPrincipal String user) {
} }

View File

@ -32,6 +32,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotatedMethod;
import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.expression.BeanResolver; import org.springframework.expression.BeanResolver;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -230,6 +231,19 @@ public class AuthenticationPrincipalArgumentResolverTests {
assertThat(result.block()).isEqualTo(principal.id); assertThat(result.block()).isEqualTo(principal.id);
} }
@Test
public void resolveArgumentWhenAliasForOnInterfaceThenInherits() {
CustomUserPrincipal principal = new CustomUserPrincipal();
given(this.authentication.getPrincipal()).willReturn(principal);
ResolvableMethod method = ResolvableMethod.on(TestController.class)
.named("showUserNoConcreteAnnotation")
.build();
MethodParameter parameter = new AnnotatedMethod(method.method()).getMethodParameters()[0];
Mono<Object> result = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.authentication));
assertThat(result.block()).isEqualTo(principal.property);
}
private MethodParameter arg0(String methodName) { private MethodParameter arg0(String methodName) {
ResolvableMethod method = ResolvableMethod.on(getClass()).named(methodName).build(); ResolvableMethod method = ResolvableMethod.on(getClass()).named(methodName).build();
return new SynthesizingMethodParameter(method.method(), 0); return new SynthesizingMethodParameter(method.method(), 0);
@ -317,6 +331,8 @@ public class AuthenticationPrincipalArgumentResolverTests {
public final int id = 1; public final int id = 1;
public final String property = "property";
public Object getPrincipal() { public Object getPrincipal() {
return this; return this;
} }
@ -340,4 +356,29 @@ public class AuthenticationPrincipalArgumentResolverTests {
} }
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal
@interface Property {
@AliasFor(attribute = "expression", annotation = AuthenticationPrincipal.class)
String value() default "id";
}
private interface TestInterface {
void showUserNoConcreteAnnotation(@Property("property") String property);
}
private static class TestController implements TestInterface {
@Override
public void showUserNoConcreteAnnotation(String user) {
}
}
} }