diff --git a/graphql-spqr-spring-boot-autoconfigure/pom.xml b/graphql-spqr-spring-boot-autoconfigure/pom.xml
index d942c4d..bec3eb4 100644
--- a/graphql-spqr-spring-boot-autoconfigure/pom.xml
+++ b/graphql-spqr-spring-boot-autoconfigure/pom.xml
@@ -45,6 +45,12 @@
true
+
+ org.springframework.security
+ spring-security-core
+ true
+
+
org.springframework.boot
spring-boot-starter-web
diff --git a/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/autoconfigure/SpringSecurityAutoConfiguration.java b/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/autoconfigure/SpringSecurityAutoConfiguration.java
new file mode 100644
index 0000000..d9861bf
--- /dev/null
+++ b/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/autoconfigure/SpringSecurityAutoConfiguration.java
@@ -0,0 +1,23 @@
+package io.leangen.graphql.spqr.spring.autoconfigure;
+
+import io.leangen.graphql.module.Module;
+import io.leangen.graphql.spqr.spring.modules.security.SpringSecurityModule;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+
+@Configuration
+@ConditionalOnClass(name = "org.springframework.security.access.AccessDeniedException")
+public class SpringSecurityAutoConfiguration {
+ @Autowired(required = false)
+ private AuthenticationTrustResolver resolver;
+
+ @Bean
+ @ConditionalOnProperty(name = "graphql.spqr.spring-security-compatible", havingValue = "true", matchIfMissing = true)
+ public Internal springSecurityModule() {
+ return new Internal<>(new SpringSecurityModule(resolver));
+ }
+}
diff --git a/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/modules/security/AccessDeniedInterceptor.java b/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/modules/security/AccessDeniedInterceptor.java
new file mode 100644
index 0000000..6459b6b
--- /dev/null
+++ b/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/modules/security/AccessDeniedInterceptor.java
@@ -0,0 +1,43 @@
+package io.leangen.graphql.spqr.spring.modules.security;
+
+import graphql.GraphQLError;
+import graphql.GraphqlErrorBuilder;
+import graphql.execution.DataFetcherResult;
+import io.leangen.graphql.execution.InvocationContext;
+import io.leangen.graphql.execution.ResolverInterceptor;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+
+public class AccessDeniedInterceptor implements ResolverInterceptor {
+ private final AuthenticationTrustResolver resolver;
+
+ public AccessDeniedInterceptor(AuthenticationTrustResolver resolver) {
+ this.resolver = resolver != null ? resolver : new AuthenticationTrustResolverImpl();
+ }
+
+ @Override
+ public Object aroundInvoke(InvocationContext context, Continuation continuation) throws Exception {
+ try {
+ return continuation.proceed(context);
+ } catch (AccessDeniedException e) {
+ GraphQLError error;
+ if (resolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {
+ error = GraphqlErrorBuilder.newError()
+ .errorType(SecurityErrorType.UNAUTHORIZED)
+ .message("Unauthorized")
+ .build();
+ } else {
+ error = GraphqlErrorBuilder.newError()
+ .errorType(SecurityErrorType.FORBIDDEN)
+ .message(String.format("Forbidden: %s", e.getMessage()))
+ .build();
+ }
+ return DataFetcherResult.newResult()
+ .error(error)
+ .build();
+ }
+ }
+}
diff --git a/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/modules/security/SecurityErrorType.java b/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/modules/security/SecurityErrorType.java
new file mode 100644
index 0000000..e393481
--- /dev/null
+++ b/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/modules/security/SecurityErrorType.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2021 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.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.leangen.graphql.spqr.spring.modules.security;
+
+import graphql.ErrorClassification;
+
+/**
+ * Common categories to use to classify for exceptions raised by
+ * resolver that can enable a client to make automated
+ * decisions.
+ *
+ * @see graphql.GraphqlErrorBuilder#errorType(ErrorClassification)
+ */
+public enum SecurityErrorType implements ErrorClassification {
+ /**
+ * Resolver did not fetch the data value due to a lack of
+ * valid authentication credentials.
+ */
+ UNAUTHORIZED,
+
+ /**
+ * Resolver refuses to authorize the fetching of the data
+ * value.
+ */
+ FORBIDDEN
+}
diff --git a/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/modules/security/SpringSecurityModule.java b/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/modules/security/SpringSecurityModule.java
new file mode 100644
index 0000000..fde9f1b
--- /dev/null
+++ b/graphql-spqr-spring-boot-autoconfigure/src/main/java/io/leangen/graphql/spqr/spring/modules/security/SpringSecurityModule.java
@@ -0,0 +1,21 @@
+package io.leangen.graphql.spqr.spring.modules.security;
+
+import io.leangen.graphql.module.Module;
+import org.springframework.security.authentication.AuthenticationTrustResolver;
+
+import java.util.Collections;
+
+public class SpringSecurityModule implements Module {
+ private final AuthenticationTrustResolver resolver;
+
+ public SpringSecurityModule(AuthenticationTrustResolver resolver) {
+ this.resolver = resolver;
+ }
+
+ @Override
+ public void setUp(SetupContext context) {
+ context.getSchemaGenerator()
+ .withResolverInterceptorFactories((config, factories) ->
+ factories.append(params -> Collections.singletonList(new AccessDeniedInterceptor(resolver))));
+ }
+}
diff --git a/graphql-spqr-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/graphql-spqr-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
index f5540e8..785d69c 100644
--- a/graphql-spqr-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/graphql-spqr-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -3,4 +3,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.leangen.graphql.spqr.spring.autoconfigure.MvcAutoConfiguration,\
io.leangen.graphql.spqr.spring.autoconfigure.ReactiveAutoConfiguration,\
io.leangen.graphql.spqr.spring.autoconfigure.SpringDataAutoConfiguration,\
+ io.leangen.graphql.spqr.spring.autoconfigure.SpringSecurityAutoConfiguration,\
io.leangen.graphql.spqr.spring.autoconfigure.WebSocketAutoConfiguration
diff --git a/graphql-spqr-spring-boot-autoconfigure/src/test/java/io/leangen/graphql/spqr/spring/test/ResolverBuilder_TestConfig.java b/graphql-spqr-spring-boot-autoconfigure/src/test/java/io/leangen/graphql/spqr/spring/test/ResolverBuilder_TestConfig.java
index 977957e..4005077 100644
--- a/graphql-spqr-spring-boot-autoconfigure/src/test/java/io/leangen/graphql/spqr/spring/test/ResolverBuilder_TestConfig.java
+++ b/graphql-spqr-spring-boot-autoconfigure/src/test/java/io/leangen/graphql/spqr/spring/test/ResolverBuilder_TestConfig.java
@@ -17,6 +17,8 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@@ -231,6 +233,35 @@ public String getName() {
}
}
+ @Component
+ @GraphQLApi
+ public static class SpringAccessDeniedComponent {
+
+ @GraphQLQuery(name = "springAccessDeniedComponent_query")
+ public List users() {
+ throw new AccessDeniedException("Access Denied");
+ }
+
+ public static class Result {
+ private final String id;
+ private final String name;
+
+ Result(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ }
+ }
+
//------------------------------------------------------------------------------
//--------------------- ResolverBuilders ---------------------------------------
diff --git a/graphql-spqr-spring-boot-autoconfigure/src/test/java/io/leangen/graphql/spqr/spring/web/GraphQLControllerTest.java b/graphql-spqr-spring-boot-autoconfigure/src/test/java/io/leangen/graphql/spqr/spring/web/GraphQLControllerTest.java
index 3c26c17..f88705c 100644
--- a/graphql-spqr-spring-boot-autoconfigure/src/test/java/io/leangen/graphql/spqr/spring/web/GraphQLControllerTest.java
+++ b/graphql-spqr-spring-boot-autoconfigure/src/test/java/io/leangen/graphql/spqr/spring/web/GraphQLControllerTest.java
@@ -3,6 +3,7 @@
import io.leangen.graphql.spqr.spring.autoconfigure.BaseAutoConfiguration;
import io.leangen.graphql.spqr.spring.autoconfigure.MvcAutoConfiguration;
import io.leangen.graphql.spqr.spring.autoconfigure.SpringDataAutoConfiguration;
+import io.leangen.graphql.spqr.spring.autoconfigure.SpringSecurityAutoConfiguration;
import io.leangen.graphql.spqr.spring.test.ResolverBuilder_TestConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -28,7 +29,7 @@
@RunWith(SpringRunner.class)
@WebMvcTest
@ContextConfiguration(classes = {BaseAutoConfiguration.class, MvcAutoConfiguration.class,
- SpringDataAutoConfiguration.class, ResolverBuilder_TestConfig.class})
+ SpringDataAutoConfiguration.class, SpringSecurityAutoConfiguration.class, ResolverBuilder_TestConfig.class})
@TestPropertySource(locations = "classpath:application.properties")
public class GraphQLControllerTest {
@@ -188,4 +189,19 @@ public void defaultControllerTest_POST_spring_page() throws Exception {
"\"springPageComponent_user_projects\":{\"pageInfo\":{\"startCursor\":\"1\",\"endCursor\":\"2\",\"hasNextPage\":true}," +
"\"edges\":[{\"node\":{\"name\":\"Project0\"}},{\"node\":{\"name\":\"Project1\"}}]}}}]}}}")));
}
+
+ @Test
+ public void defaultControllerTest_POST_spring_access_denied() throws Exception {
+ String withPage = "{\n" +
+ " springAccessDeniedComponent_query {\n" +
+ " id\n" +
+ " name\n" +
+ " }\n" +
+ "}";
+
+ mockMvc.perform(get("/" + apiContext)
+ .param("query", withPage))
+ .andExpect(status().isOk())
+ .andExpect(content().string(containsString("\"classification\":\"FORBIDDEN\"")));
+ }
}