Add security package with basic infrastructure
Closes gh-80, gh-82
This commit is contained in:
@@ -16,8 +16,14 @@
|
||||
|
||||
package io.spring.sample.graphql;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SampleApplication {
|
||||
@@ -25,4 +31,13 @@ public class SampleApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebInterceptor interceptor() {
|
||||
return (input, next) -> {
|
||||
// Switch threads to prove ThreadLocal context propagation works
|
||||
return Mono.delay(Duration.ofMillis(10)).flatMap(aLong -> next.handle(input));
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package io.spring.sample.graphql;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.execution.ThreadLocalAccessor;
|
||||
import org.springframework.graphql.security.SecurityContextThreadLocalAccessor;
|
||||
import org.springframework.graphql.security.SecurityDataFetcherExceptionResolver;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@@ -38,4 +41,14 @@ public class SecurityConfig {
|
||||
return new InMemoryUserDetailsManager(rob, admin);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityDataFetcherExceptionResolver dataFetcherExceptionResolver() {
|
||||
return new SecurityDataFetcherExceptionResolver();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ThreadLocalAccessor threadLocalAccessor() {
|
||||
return new SecurityContextThreadLocalAccessor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package io.spring.sample.graphql;
|
||||
|
||||
import org.springframework.graphql.execution.ThreadLocalAccessor;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class SecurityContextThreadLocalAccessor implements ThreadLocalAccessor {
|
||||
|
||||
private static final String KEY = SecurityContext.class.getName();
|
||||
|
||||
@Override
|
||||
public void extractValues(Map<String, Object> container) {
|
||||
container.put(KEY, SecurityContextHolder.getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreValues(Map<String, Object> values) {
|
||||
if (values.containsKey(KEY)) {
|
||||
SecurityContextHolder.setContext((SecurityContext) values.get(KEY));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetValues(Map<String, Object> values) {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package io.spring.sample.graphql;
|
||||
|
||||
import graphql.GraphQLError;
|
||||
import graphql.GraphqlErrorBuilder;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
|
||||
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
|
||||
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.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@Component
|
||||
public class SecurityDataFetcherExceptionResolver extends DataFetcherExceptionResolverAdapter {
|
||||
|
||||
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
|
||||
|
||||
@Override
|
||||
protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
|
||||
if (ex instanceof AuthenticationException) {
|
||||
return unauthorized(env);
|
||||
}
|
||||
if (ex instanceof AccessDeniedException) {
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
Authentication authentication = context.getAuthentication();
|
||||
if (this.authenticationTrustResolver.isAnonymous(authentication)) {
|
||||
return unauthorized(env);
|
||||
}
|
||||
return forbidden(env);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) {
|
||||
Assert.notNull(authenticationTrustResolver, "authenticationTrustResolver cannot be null");
|
||||
this.authenticationTrustResolver = authenticationTrustResolver;
|
||||
}
|
||||
|
||||
private GraphQLError unauthorized(DataFetchingEnvironment environment) {
|
||||
return GraphqlErrorBuilder.newError(environment)
|
||||
.errorType(ErrorType.UNAUTHORIZED)
|
||||
.message("Unauthorized")
|
||||
.build();
|
||||
}
|
||||
|
||||
private GraphQLError forbidden(DataFetchingEnvironment environment) {
|
||||
return GraphqlErrorBuilder.newError(environment)
|
||||
.errorType(ErrorType.FORBIDDEN)
|
||||
.message("Forbidden")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user