Correct order of authentication resolvers

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

View File

@@ -277,6 +277,10 @@ public class AnnotatedControllerConfigurer implements ApplicationContextAware, I
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());
@@ -284,9 +288,7 @@ public class AnnotatedControllerConfigurer implements ApplicationContextAware, I
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

@@ -44,7 +44,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;
@@ -79,7 +81,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();
@@ -121,10 +123,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 }",
@@ -217,20 +219,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);
@@ -288,6 +317,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 "";
}
}
}