diff --git a/samples/webflux-security/src/main/java/io/spring/sample/graphql/SecurityDataFetcherExceptionResolver.java b/samples/webflux-security/src/main/java/io/spring/sample/graphql/SecurityDataFetcherExceptionResolver.java new file mode 100644 index 00000000..eb13ffb0 --- /dev/null +++ b/samples/webflux-security/src/main/java/io/spring/sample/graphql/SecurityDataFetcherExceptionResolver.java @@ -0,0 +1,47 @@ +package io.spring.sample.graphql; + +import graphql.ErrorClassification; +import graphql.GraphQLError; +import graphql.GraphqlErrorBuilder; +import graphql.schema.DataFetchingEnvironment; +import org.springframework.graphql.execution.DataFetcherExceptionResolver; +import org.springframework.graphql.execution.ErrorType; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.AuthenticationTrustResolverImpl; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.List; + +@Component +public class SecurityDataFetcherExceptionResolver implements DataFetcherExceptionResolver { + private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); + + @Override + public Mono> resolveException(Throwable exception, DataFetchingEnvironment environment) { + if (exception instanceof AuthenticationException) { + + } + if (exception instanceof AccessDeniedException) { + return ReactiveSecurityContextHolder.getContext() + .map(SecurityContext::getAuthentication) + .filter(a -> !this.authenticationTrustResolver.isAnonymous(a)) + .flatMap(anonymous -> forbidden(environment)) + .switchIfEmpty(unauthorized(environment)); + } + return Mono.empty(); + } + + private Mono> unauthorized(DataFetchingEnvironment environment) { + return Mono.fromCallable(() -> Arrays.asList(GraphqlErrorBuilder.newError(environment).errorType(ErrorType.UNAUTHORIZED).message("Unauthorized").build())); + } + + private Mono> forbidden(DataFetchingEnvironment environment) { + return Mono.fromCallable(() -> Arrays.asList(GraphqlErrorBuilder.newError(environment).errorType(ErrorType.FORBIDDEN).message("Forbidden").build())); + } +} diff --git a/samples/webflux-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java b/samples/webflux-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java index 24ef3c33..060b09af 100644 --- a/samples/webflux-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java +++ b/samples/webflux-security/src/test/java/io/spring/sample/graphql/SampleApplicationTests.java @@ -37,6 +37,59 @@ class SampleApplicationTests { .build(); } + @Test + void printError() { + String query = "{" + + " employees{ " + + " name" + + " salary" + + " }" + + "}"; + + + client.post().uri("") + .bodyValue("{ \"query\": \"" + query + "\"}") + .exchange() + .expectStatus().isOk() + .expectBody(String.class) + .consumeWith(System.out::println); + + } + + @Test + void anonoymousThenUnauthorized() { + String query = "{" + + " employees{ " + + " name" + + " salary" + + " }" + + "}"; + + client.post().uri("") + .bodyValue("{ \"query\": \"" + query + "\"}") + .exchange() + .expectStatus().isOk() + .expectBody().jsonPath("errors[0].extensions.classification").isEqualTo("UNAUTHORIZED"); + + } + + @Test + void userRoleThenForbidden() { + String query = "{" + + " employees{ " + + " name" + + " salary" + + " }" + + "}"; + + client.post().uri("") + .headers(h -> h.setBasicAuth("rob", "rob")) + .bodyValue("{ \"query\": \"" + query + "\"}") + .exchange() + .expectStatus().isOk() + .expectBody().jsonPath("errors[0].extensions.classification").isEqualTo("FORBIDDEN"); + } + @Test void canQueryName() { String query = "{" +