Add security package with basic infrastructure

Closes gh-80, gh-82
This commit is contained in:
Rossen Stoyanchev
2021-07-05 21:00:43 +01:00
parent aea229778c
commit b594c19526
12 changed files with 281 additions and 146 deletions

View File

@@ -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));
};
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}