Correct order of authentication resolvers

Closes gh-982
This commit is contained in:
rstoyanchev
2024-06-18 07:18:31 +01:00
parent ac720eb9a2
commit 635066436e
2 changed files with 57 additions and 10 deletions

View File

@@ -190,6 +190,10 @@ public class AnnotatedControllerConfigurer
resolvers.addResolver(new ArgumentsMethodArgumentResolver(argumentBinder));
resolvers.addResolver(new ContextValueMethodArgumentResolver());
resolvers.addResolver(new LocalContextValueMethodArgumentResolver());
if (springSecurityPresent) {
ApplicationContext context = obtainApplicationContext();
resolvers.addResolver(new AuthenticationPrincipalArgumentResolver(new BeanFactoryResolver(context)));
}
// Type based
resolvers.addResolver(new DataFetchingEnvironmentMethodArgumentResolver());
@@ -197,9 +201,7 @@ public class AnnotatedControllerConfigurer
addSubrangeMethodArgumentResolver(resolvers);
addSortMethodArgumentResolver(resolvers);
if (springSecurityPresent) {
ApplicationContext context = obtainApplicationContext();
resolvers.addResolver(new PrincipalMethodArgumentResolver());
resolvers.addResolver(new AuthenticationPrincipalArgumentResolver(new BeanFactoryResolver(context)));
}
if (KotlinDetector.isKotlinPresent()) {
resolvers.addResolver(new ContinuationHandlerMethodArgumentResolver());

View File

@@ -45,7 +45,9 @@ import org.springframework.graphql.execution.DataFetcherExceptionResolver;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
@@ -82,7 +84,7 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
@Test
void supportsParameter() {
Method method = ClassUtils.getMethod(SchemaMappingPrincipalMethodArgumentResolverTests.class, "handle", (Class<?>[]) null);
Method method = ClassUtils.getMethod(getClass(), "handle", (Class<?>[]) null);
assertThat(this.resolver.supportsParameter(new MethodParameter(method, 0))).isTrue();
assertThat(this.resolver.supportsParameter(new MethodParameter(method, 1))).isTrue();
assertThat(this.resolver.supportsParameter(new MethodParameter(method, 2))).isFalse();
@@ -124,10 +126,10 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
@Test
void nonNullPrincipalRequiresSecurityContext() {
DataFetcherExceptionResolver exceptionResolver =
DataFetcherExceptionResolver.forSingleError((ex, env) -> GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage())
.errorType(ErrorType.UNAUTHORIZED)
.build());
DataFetcherExceptionResolver.forSingleError((ex, env) -> GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage())
.errorType(ErrorType.UNAUTHORIZED)
.build());
Mono<ExecutionGraphQlResponse> responseMono = executeAsync(
"type Query { greetingMono: String }", "{ greetingMono }",
@@ -220,20 +222,47 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
}
@Nested
class AuthenticationPrincipalTests {
@Test // gh-982
void query() {
Authentication authentication = new UsernamePasswordAuthenticationToken(new GraphQlPrincipal(), null);
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
try {
String field = "greetingAuthenticationPrincipal";
Mono<ExecutionGraphQlResponse> responseMono = executeAsync(
"type Query { " + field + " : String }", "{ " + field + " }", threadLocalContextWriter);
String greeting = ResponseHelper.forResponse(responseMono).toEntity(field, String.class);
assertThat(greeting).isEqualTo("Hello");
assertThat(greetingController.principal()).isSameAs(authentication.getPrincipal());
}
finally {
SecurityContextHolder.clearContext();
}
}
}
private Mono<ExecutionGraphQlResponse> executeAsync(
String schema, String document, Function<Context, Context> contextWriter) {
return executeAsync(schema, document, contextWriter, null);
}
private Mono<ExecutionGraphQlResponse> executeAsync(
String schema, String document, Function<Context, Context> contextWriter, @Nullable DataFetcherExceptionResolver exceptionResolver) {
String schema, String document, Function<Context, Context> contextWriter,
@Nullable DataFetcherExceptionResolver exceptionResolver) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(GreetingController.class, () -> greetingController);
context.refresh();
GraphQlSetup graphQlSetup = GraphQlSetup.schemaContent(schema)
.runtimeWiringForAnnotatedControllers(context);
GraphQlSetup graphQlSetup =
GraphQlSetup.schemaContent(schema).runtimeWiringForAnnotatedControllers(context);
if (exceptionResolver != null) {
graphQlSetup.exceptionResolver(exceptionResolver);
@@ -291,6 +320,22 @@ public class SchemaMappingPrincipalMethodArgumentResolverTests {
return Flux.just("Hello", "Hi");
}
@QueryMapping
String greetingAuthenticationPrincipal(@AuthenticationPrincipal GraphQlPrincipal principal) {
this.principal = principal;
return "Hello";
}
}
private static final class GraphQlPrincipal implements Principal {
@Override
public String getName() {
return "";
}
}
}