Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit 9f6e1c5

Browse files
authored
Merge pull request #635 from graphql-java-kickstart/security-async-task-decorator
fix(#632): propagate spring security context
2 parents ca66b31 + 9d1779b commit 9f6e1c5

File tree

6 files changed

+127
-7
lines changed

6 files changed

+127
-7
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ TARGET_COMPATIBILITY=1.8
3333
LIB_GRAPHQL_JAVA_VER=16.2
3434
LIB_EXTENDED_SCALARS_VER=16.0.1
3535
LIB_SPRING_BOOT_VER=2.4.5
36-
LIB_GRAPHQL_SERVLET_VER=11.1.1
36+
LIB_GRAPHQL_SERVLET_VER=11.2.0-SNAPSHOT
3737
LIB_GRAPHQL_JAVA_TOOLS_VER=11.0.1
3838
LIB_GRAPHQL_ANNOTATIONS_VER=8.3
3939
LIB_REFLECTIONS_VER=0.9.11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package graphql.kickstart.autoconfigure.web.servlet;
2+
3+
import lombok.Data;
4+
import org.springframework.boot.context.properties.ConfigurationProperties;
5+
6+
@Data
7+
@ConfigurationProperties(prefix = "graphql.servlet.async")
8+
public class AsyncServletProperties {
9+
10+
private boolean enabled = true;
11+
private long timeout = 30000;
12+
private boolean delegateSecurityContext = true;
13+
private Threads threads = new Threads();
14+
15+
@Data
16+
static class Threads {
17+
private int min = 10;
18+
private int max = 200;
19+
private String namePrefix = "graphql-exec-";
20+
}
21+
}

graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/web/servlet/GraphQLServletProperties.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ public class GraphQLServletProperties {
3333
private boolean exceptionHandlersEnabled = false;
3434
private long subscriptionTimeout = 0;
3535
private ContextSetting contextSetting = ContextSetting.PER_QUERY_WITH_INSTRUMENTATION;
36-
private long asyncTimeout = 30000;
37-
private boolean asyncModeEnabled = true;
36+
/** @deprecated Use <tt>graphql.servlet.async.timeout</tt> instead */
37+
@Deprecated private Long asyncTimeout;
38+
/** @deprecated Use <tt>graphql.servlet.async.enabled</tt> instead */
39+
@Deprecated private Boolean asyncModeEnabled;
40+
3841
private String tracingEnabled = "false";
3942
private boolean actuatorMetrics;
4043
private Integer maxQueryComplexity;

graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/web/servlet/GraphQLWebAutoConfiguration.java

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import graphql.kickstart.execution.config.ObjectMapperProvider;
4444
import graphql.kickstart.execution.error.GraphQLErrorHandler;
4545
import graphql.kickstart.servlet.AbstractGraphQLHttpServlet;
46+
import graphql.kickstart.servlet.AsyncTaskDecorator;
4647
import graphql.kickstart.servlet.GraphQLConfiguration;
4748
import graphql.kickstart.servlet.GraphQLHttpServlet;
4849
import graphql.kickstart.servlet.cache.GraphQLResponseCacheManager;
@@ -61,11 +62,13 @@
6162
import java.util.List;
6263
import java.util.Map;
6364
import java.util.Map.Entry;
65+
import java.util.concurrent.Executor;
6466
import javax.servlet.MultipartConfigElement;
6567
import lombok.RequiredArgsConstructor;
6668
import lombok.extern.slf4j.Slf4j;
6769
import org.springframework.beans.factory.ObjectProvider;
6870
import org.springframework.beans.factory.annotation.Autowired;
71+
import org.springframework.beans.factory.annotation.Qualifier;
6972
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
7073
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
7174
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -81,6 +84,7 @@
8184
import org.springframework.context.annotation.Configuration;
8285
import org.springframework.context.annotation.Import;
8386
import org.springframework.http.HttpMethod;
87+
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
8488
import org.springframework.web.cors.CorsConfiguration;
8589
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
8690
import org.springframework.web.filter.CorsFilter;
@@ -101,14 +105,15 @@
101105
havingValue = "true",
102106
matchIfMissing = true)
103107
@AutoConfigureAfter({GraphQLJavaToolsAutoConfiguration.class, JacksonAutoConfiguration.class})
104-
@EnableConfigurationProperties({GraphQLServletProperties.class})
108+
@EnableConfigurationProperties({GraphQLServletProperties.class, AsyncServletProperties.class})
105109
public class GraphQLWebAutoConfiguration {
106110

107111
public static final String QUERY_EXECUTION_STRATEGY = "queryExecutionStrategy";
108112
public static final String MUTATION_EXECUTION_STRATEGY = "mutationExecutionStrategy";
109113
public static final String SUBSCRIPTION_EXECUTION_STRATEGY = "subscriptionExecutionStrategy";
110114

111115
private final GraphQLServletProperties graphQLServletProperties;
116+
private final AsyncServletProperties asyncServletProperties;
112117
private final ErrorHandlerSupplier errorHandlerSupplier = new ErrorHandlerSupplier(null);
113118

114119
@Bean
@@ -293,7 +298,13 @@ public GraphQLConfiguration graphQLServletConfiguration(
293298
GraphQLObjectMapper graphQLObjectMapper,
294299
@Autowired(required = false) List<GraphQLServletListener> listeners,
295300
@Autowired(required = false) BatchInputPreProcessor batchInputPreProcessor,
296-
@Autowired(required = false) GraphQLResponseCacheManager responseCacheManager) {
301+
@Autowired(required = false) GraphQLResponseCacheManager responseCacheManager,
302+
@Autowired(required = false) AsyncTaskDecorator asyncTaskDecorator,
303+
@Autowired(required = false) @Qualifier("graphqlAsyncTaskExecutor") Executor asyncExecutor) {
304+
long asyncTimeout =
305+
graphQLServletProperties.getAsyncTimeout() != null
306+
? graphQLServletProperties.getAsyncTimeout()
307+
: asyncServletProperties.getTimeout();
297308
return GraphQLConfiguration.with(invocationInputFactory)
298309
.with(graphQLInvoker)
299310
.with(graphQLObjectMapper)
@@ -302,10 +313,34 @@ public GraphQLConfiguration graphQLServletConfiguration(
302313
.with(batchInputPreProcessor)
303314
.with(graphQLServletProperties.getContextSetting())
304315
.with(responseCacheManager)
305-
.asyncTimeout(graphQLServletProperties.getAsyncTimeout())
316+
.asyncTimeout(asyncTimeout)
317+
.with(asyncTaskDecorator)
318+
.asyncCorePoolSize(asyncServletProperties.getThreads().getMin())
319+
.asyncCorePoolSize(asyncServletProperties.getThreads().getMax())
320+
.with(asyncExecutor)
306321
.build();
307322
}
308323

324+
@Bean("graphqlAsyncTaskExecutor")
325+
@ConditionalOnMissingBean(name = "graphqlAsyncTaskExecutor")
326+
public Executor threadPoolTaskExecutor() {
327+
if (isAsyncModeEnabled()) {
328+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
329+
executor.setCorePoolSize(asyncServletProperties.getThreads().getMin());
330+
executor.setMaxPoolSize(asyncServletProperties.getThreads().getMax());
331+
executor.setThreadNamePrefix(asyncServletProperties.getThreads().getNamePrefix());
332+
executor.initialize();
333+
return executor;
334+
}
335+
return null;
336+
}
337+
338+
private boolean isAsyncModeEnabled() {
339+
return graphQLServletProperties.getAsyncModeEnabled() != null
340+
? graphQLServletProperties.getAsyncModeEnabled()
341+
: asyncServletProperties.isEnabled();
342+
}
343+
309344
@Bean
310345
@ConditionalOnMissingBean
311346
public GraphQLHttpServlet graphQLHttpServlet(GraphQLConfiguration graphQLConfiguration) {
@@ -323,7 +358,11 @@ public ServletRegistrationBean<AbstractGraphQLHttpServlet> graphQLServletRegistr
323358
} else {
324359
registration.setMultipartConfig(new MultipartConfigElement(""));
325360
}
326-
registration.setAsyncSupported(graphQLServletProperties.isAsyncModeEnabled());
361+
if (graphQLServletProperties.getAsyncModeEnabled() != null) {
362+
registration.setAsyncSupported(graphQLServletProperties.getAsyncModeEnabled());
363+
} else {
364+
registration.setAsyncSupported(asyncServletProperties.isEnabled());
365+
}
327366
return registration;
328367
}
329368
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package graphql.kickstart.autoconfigure.web.servlet;
2+
3+
import graphql.kickstart.autoconfigure.web.OnSchemaOrSchemaProviderBean;
4+
import java.util.concurrent.Executor;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
7+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
8+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
9+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
10+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
11+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
12+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
13+
import org.springframework.context.annotation.Bean;
14+
import org.springframework.context.annotation.Conditional;
15+
import org.springframework.context.annotation.Configuration;
16+
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
17+
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
18+
import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor;
19+
import org.springframework.web.servlet.DispatcherServlet;
20+
21+
@Configuration
22+
@RequiredArgsConstructor
23+
@ConditionalOnWebApplication(type = Type.SERVLET)
24+
@Conditional(OnSchemaOrSchemaProviderBean.class)
25+
@ConditionalOnProperty(
26+
value = "graphql.servlet.enabled",
27+
havingValue = "true",
28+
matchIfMissing = true)
29+
@AutoConfigureBefore(GraphQLWebAutoConfiguration.class)
30+
@ConditionalOnClass({DispatcherServlet.class, DefaultAuthenticationEventPublisher.class})
31+
@EnableConfigurationProperties({GraphQLServletProperties.class, AsyncServletProperties.class})
32+
public class GraphQLWebSecurityAutoConfiguration {
33+
34+
private final GraphQLServletProperties graphqlServletProperties;
35+
private final AsyncServletProperties asyncServletProperties;
36+
37+
@Bean("graphqlAsyncTaskExecutor")
38+
@ConditionalOnMissingBean(name = "graphqlAsyncTaskExecutor")
39+
public Executor threadPoolTaskExecutor() {
40+
if (isAsyncModeEnabled() && asyncServletProperties.isDelegateSecurityContext()) {
41+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
42+
executor.setCorePoolSize(asyncServletProperties.getThreads().getMin());
43+
executor.setMaxPoolSize(asyncServletProperties.getThreads().getMax());
44+
executor.setThreadNamePrefix(asyncServletProperties.getThreads().getNamePrefix());
45+
executor.initialize();
46+
return new DelegatingSecurityContextAsyncTaskExecutor(executor);
47+
}
48+
return null;
49+
}
50+
51+
private boolean isAsyncModeEnabled() {
52+
return graphqlServletProperties.getAsyncModeEnabled() != null
53+
? graphqlServletProperties.getAsyncModeEnabled()
54+
: asyncServletProperties.isEnabled();
55+
}
56+
}

graphql-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ org.springframework.context.ApplicationContextInitializer=\
22
graphql.kickstart.autoconfigure.web.servlet.GraphQLExtendedScalarsInitializer
33
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
44
graphql.kickstart.autoconfigure.web.servlet.GraphQLWebAutoConfiguration,\
5+
graphql.kickstart.autoconfigure.web.servlet.GraphQLWebSecurityAutoConfiguration,\
56
graphql.kickstart.autoconfigure.web.servlet.GraphQLWebsocketAutoConfiguration,\
67
graphql.kickstart.autoconfigure.web.servlet.GraphQLInstrumentationAutoConfiguration,\
78
graphql.kickstart.autoconfigure.web.reactive.GraphQLSpringWebfluxAutoConfiguration,\

0 commit comments

Comments
 (0)