Add Spring Security auto-configuration

This commit adds two new auto-configuration classes that provide the
relevant Spring Security infrastructure for GraphQL applications:

* a `ThreadLocalAccessor` that propagates the security context in
Spring MVC applications
* `DataFetcherExceptionResolver` implementations that resolve security
  exception from data fetchers, for both MVC and WebFlux

Closes gh-81
This commit is contained in:
Brian Clozel
2021-07-06 13:29:06 +02:00
parent 58d65d7a4c
commit f1bc239b57
9 changed files with 560 additions and 8 deletions

View File

@@ -38,7 +38,7 @@ configure(moduleProjects) {
mavenBom "io.projectreactor:reactor-bom:2020.0.7"
mavenBom "org.springframework:spring-framework-bom:5.3.7"
mavenBom "org.springframework.data:spring-data-bom:2021.0.1"
mavenBom "org.springframework.security:spring-security-bom:5.5.0"
mavenBom "org.springframework.security:spring-security-bom:5.5.1"
mavenBom "org.junit:junit-bom:5.7.2"
}
dependencies {

View File

@@ -38,6 +38,9 @@ dependencies {
compileOnly 'io.micrometer:micrometer-core'
compileOnly 'org.springframework.boot:spring-boot-actuator-autoconfigure'
compileOnly 'org.springframework.security:spring-security-config'
compileOnly 'org.springframework.security:spring-security-web'
compileOnly project(':spring-graphql-test')
compileOnly 'org.springframework.boot:spring-boot-test'
@@ -57,6 +60,9 @@ dependencies {
testImplementation 'org.apache.tomcat.embed:tomcat-embed-websocket'
testImplementation 'org.springframework.boot:spring-boot-actuator-autoconfigure'
testImplementation 'io.micrometer:micrometer-core'
testImplementation 'org.springframework.security:spring-security-config'
testImplementation 'org.springframework.security:spring-security-web'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2020-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 org.springframework.graphql.boot.security;
import graphql.GraphQL;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.boot.GraphQlWebFluxAutoConfiguration;
import org.springframework.graphql.security.ReactiveSecurityDataFetcherExceptionResolver;
import org.springframework.graphql.web.webflux.GraphQlHttpHandler;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
/**
* {@link EnableAutoConfiguration Auto-configuration} for enabling Security support
* for Spring GraphQL with WebFlux.
*
* @author Brian Clozel
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass({GraphQL.class, GraphQlHttpHandler.class, EnableWebFluxSecurity.class})
@ConditionalOnBean(GraphQlHttpHandler.class)
@AutoConfigureAfter(GraphQlWebFluxAutoConfiguration.class)
public class GraphQlWebFluxSecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ReactiveSecurityDataFetcherExceptionResolver reactiveSecurityDataFetcherExceptionResolver() {
return new ReactiveSecurityDataFetcherExceptionResolver();
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2020-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 org.springframework.graphql.boot.security;
import graphql.GraphQL;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.boot.GraphQlWebMvcAutoConfiguration;
import org.springframework.graphql.security.SecurityContextThreadLocalAccessor;
import org.springframework.graphql.security.SecurityDataFetcherExceptionResolver;
import org.springframework.graphql.web.webmvc.GraphQlHttpHandler;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
/**
* {@link EnableAutoConfiguration Auto-configuration} for enabling Security support
* for Spring GraphQL with MVC.
*
* @author Brian Clozel
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({GraphQL.class, GraphQlHttpHandler.class, EnableWebSecurity.class})
@ConditionalOnBean(GraphQlHttpHandler.class)
@AutoConfigureAfter(GraphQlWebMvcAutoConfiguration.class)
public class GraphQlWebMvcSecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SecurityDataFetcherExceptionResolver securityDataFetcherExceptionResolver() {
return new SecurityDataFetcherExceptionResolver();
}
@Bean
@ConditionalOnMissingBean
public SecurityContextThreadLocalAccessor securityContextThreadLocalAccessor() {
return new SecurityContextThreadLocalAccessor();
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2020-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.
*/
/**
* Auto-configuration classes to configure
* {@link org.springframework.graphql.security.SecurityDataFetcherExceptionResolver},
* {@link org.springframework.graphql.security.SecurityContextThreadLocalAccessor} for Security support.
*/
@NonNullApi
@NonNullFields
package org.springframework.graphql.boot.security;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@@ -1,9 +1,11 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.graphql.boot.actuate.metrics.GraphQlMetricsAutoConfiguration,\
org.springframework.graphql.boot.GraphQlAutoConfiguration,\
org.springframework.graphql.boot.GraphQlServiceAutoConfiguration,\
org.springframework.graphql.boot.GraphQlWebFluxAutoConfiguration,\
org.springframework.graphql.boot.GraphQlWebMvcAutoConfiguration
org.springframework.graphql.boot.GraphQlWebMvcAutoConfiguration,\
org.springframework.graphql.boot.actuate.metrics.GraphQlMetricsAutoConfiguration,\
org.springframework.graphql.boot.security.GraphQlWebFluxSecurityAutoConfiguration,\
org.springframework.graphql.boot.security.GraphQlWebMvcSecurityAutoConfiguration
# Spring Test @AutoConfigureGraphQlTester
org.springframework.graphql.boot.test.tester.AutoConfigureGraphQlTester=\

View File

@@ -22,6 +22,8 @@ import java.util.List;
import graphql.schema.DataFetcher;
import reactor.core.publisher.Flux;
import org.springframework.lang.Nullable;
public final class GraphQlDataFetchers {
private static List<Book> books = Arrays.asList(new Book("book-1", "GraphQL for beginners", 100, "John GraphQL"),
@@ -33,13 +35,20 @@ public final class GraphQlDataFetchers {
}
public static DataFetcher getBookByIdDataFetcher() {
return (environment) -> books.stream().filter((book) -> book.getId().equals(environment.getArgument("id")))
.findFirst().orElse(null);
return (environment) -> getBookById(environment.getArgument("id"));
}
public static DataFetcher getBooksOnSale() {
return (environment) -> Flux.fromIterable(books)
.filter((book) -> book.getPageCount() >= (int) environment.getArgument("minPages"));
public static DataFetcher getBooksOnSaleDataFetcher() {
return (environment) -> getBooksOnSale(environment.getArgument("minPages"));
}
@Nullable
public static Book getBookById(String id) {
return books.stream().filter((book) -> book.getId().equals(id)).findFirst().orElse(null);
}
public static Flux<Book> getBooksOnSale(int minPages) {
return Flux.fromIterable(books).filter((book) -> book.getPageCount() >= minPages);
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright 2020-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 org.springframework.graphql.boot.security;
import java.util.Collections;
import java.util.function.Consumer;
import graphql.schema.idl.TypeRuntimeWiring;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.boot.Book;
import org.springframework.graphql.boot.GraphQlAutoConfiguration;
import org.springframework.graphql.boot.GraphQlDataFetchers;
import org.springframework.graphql.boot.GraphQlServiceAutoConfiguration;
import org.springframework.graphql.boot.GraphQlWebFluxAutoConfiguration;
import org.springframework.graphql.boot.RuntimeWiringBuilderCustomizer;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.graphql.security.ReactiveSecurityDataFetcherExceptionResolver;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* Tests for {@link GraphQlWebFluxSecurityAutoConfiguration}.
* @author Brian Clozel
*/
class GraphQlWebFluxSecurityAutoConfigurationTests {
private static final String BASE_URL = "https://spring.example.org/graphql";
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class,
CodecsAutoConfiguration.class, JacksonAutoConfiguration.class, GraphQlAutoConfiguration.class,
GraphQlServiceAutoConfiguration.class, GraphQlWebFluxAutoConfiguration.class,
GraphQlWebFluxSecurityAutoConfiguration.class, ReactiveSecurityAutoConfiguration.class))
.withUserConfiguration(DataFetchersConfiguration.class, SecurityConfig.class)
.withPropertyValues(
"spring.main.web-application-type=reactive",
"spring.graphql.schema.locations=classpath:books/");
@Test
void contributesExceptionResolver() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(ReactiveSecurityDataFetcherExceptionResolver.class);
});
}
@Test
void anonymousUserShouldBeUnauthorized() {
testWithWebClient((client) -> {
String query = "{" +
" bookById(id: \\\"book-1\\\"){ " +
" id" +
" name" +
" pageCount" +
" author" +
" }" +
"}";
client.post().uri("").bodyValue("{ \"query\": \"" + query + "\"}")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("data.bookById.name")
.doesNotExist()
.jsonPath("errors[0].extensions.classification").isEqualTo(ErrorType.UNAUTHORIZED.toString());
});
}
@Test
void authenticatedUserShouldGetData() {
testWithWebClient((client) -> {
String query = "{" +
" bookById(id: \\\"book-1\\\"){ " +
" id" +
" name" +
" pageCount" +
" author" +
" }" +
"}";
client.post().uri("").headers(headers -> headers.setBasicAuth("rob", "rob"))
.bodyValue("{ \"query\": \"" + query + "\"}")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("data.bookById.name")
.isEqualTo("GraphQL for beginners")
.jsonPath("errors[0].extensions.classification").doesNotExist();
});
}
private void testWithWebClient(Consumer<WebTestClient> consumer) {
this.contextRunner.run((context) -> {
WebTestClient client = WebTestClient.bindToApplicationContext(context)
.configureClient()
.defaultHeaders((headers) -> {
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
})
.baseUrl(BASE_URL)
.build();
consumer.accept(client);
});
}
@Configuration(proxyBeanMethods = false)
static class DataFetchersConfiguration {
@Bean
RuntimeWiringBuilderCustomizer bookDataFetcher(BookService bookService) {
return (builder) -> builder.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", environment -> bookService.getBookdById(environment.getArgument("id"))));
}
@Bean
BookService bookService() {
return new BookService();
}
}
static class BookService {
@PreAuthorize("hasRole('USER')")
@Nullable
public Mono<Book> getBookdById(String id) {
return Mono.justOrEmpty(GraphQlDataFetchers.getBookById(id));
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
static class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http
.csrf(spec -> spec.disable())
// Demonstrate that method security works
// Best practice to use both for defense in depth
.authorizeExchange(requests -> requests.anyExchange().permitAll())
.httpBasic(withDefaults())
.build();
}
@Bean
@SuppressWarnings("deprecation")
public MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build();
return new MapReactiveUserDetailsService(rob, admin);
}
}
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2020-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 org.springframework.graphql.boot.security;
import graphql.schema.idl.TypeRuntimeWiring;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.boot.Book;
import org.springframework.graphql.boot.GraphQlAutoConfiguration;
import org.springframework.graphql.boot.GraphQlDataFetchers;
import org.springframework.graphql.boot.GraphQlServiceAutoConfiguration;
import org.springframework.graphql.boot.GraphQlWebMvcAutoConfiguration;
import org.springframework.graphql.boot.RuntimeWiringBuilderCustomizer;
import org.springframework.graphql.execution.ErrorType;
import org.springframework.graphql.security.SecurityContextThreadLocalAccessor;
import org.springframework.graphql.security.SecurityDataFetcherExceptionResolver;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.security.access.prepost.PreAuthorize;
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;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link GraphQlWebMvcSecurityAutoConfiguration}.
* @author Brian Clozel
*/
class GraphQlWebMvcSecurityAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DispatcherServletAutoConfiguration.class,
WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
JacksonAutoConfiguration.class, GraphQlAutoConfiguration.class,
GraphQlServiceAutoConfiguration.class, GraphQlWebMvcAutoConfiguration.class,
GraphQlWebMvcSecurityAutoConfiguration.class, SecurityAutoConfiguration.class))
.withUserConfiguration(DataFetchersConfiguration.class, SecurityConfig.class)
.withPropertyValues(
"spring.main.web-application-type=servlet",
"spring.graphql.schema.locations=classpath:books/");
@Test
void contributesSecurityComponents() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(SecurityDataFetcherExceptionResolver.class);
assertThat(context).hasSingleBean(SecurityContextThreadLocalAccessor.class);
});
}
@Test
void anonymousUserShouldBeUnauthorized() {
testWith((mockMvc) -> {
String query = "{" +
" bookById(id: \\\"book-1\\\"){ " +
" id" +
" name" +
" pageCount" +
" author" +
" }" +
"}";
MvcResult result = mockMvc.perform(post("/graphql").content("{\"query\": \"" + query + "\"}")).andReturn();
mockMvc.perform(asyncDispatch(result))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("data.bookById.name").doesNotExist())
.andExpect(jsonPath("errors[0].extensions.classification").value(ErrorType.UNAUTHORIZED.toString()));
});
}
@Test
void authenticatedUserShouldGetData() {
testWith((mockMvc) -> {
String query = "{" +
" bookById(id: \\\"book-1\\\"){ " +
" id" +
" name" +
" pageCount" +
" author" +
" }" +
"}";
MvcResult result = mockMvc.perform(post("/graphql").content("{\"query\": \"" + query + "\"}").with(user("rob"))).andReturn();
mockMvc.perform(asyncDispatch(result))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("data.bookById.name").value("GraphQL for beginners"))
.andExpect(jsonPath("errors").doesNotExist());
});
}
private void testWith(MockMvcConsumer mockMvcConsumer) {
this.contextRunner.run((context) -> {
MediaType mediaType = MediaType.APPLICATION_JSON;
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context)
.defaultRequest(post("/graphql").contentType(mediaType).accept(mediaType))
.apply(springSecurity())
.build();
mockMvcConsumer.accept(mockMvc);
});
}
private interface MockMvcConsumer {
void accept(MockMvc mockMvc) throws Exception;
}
@Configuration(proxyBeanMethods = false)
static class DataFetchersConfiguration {
@Bean
RuntimeWiringBuilderCustomizer bookDataFetcher(BookService bookService) {
return (builder) -> builder.type(TypeRuntimeWiring.newTypeWiring("Query")
.dataFetcher("bookById", environment -> bookService.getBookdById(environment.getArgument("id"))));
}
@Bean
BookService bookService() {
return new BookService();
}
}
static class BookService {
@PreAuthorize("hasRole('USER')")
@Nullable
public Book getBookdById(String id) {
return GraphQlDataFetchers.getBookById(id);
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
static class SecurityConfig {
@Bean
DefaultSecurityFilterChain springWebFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(c -> c.disable())
// Demonstrate that method security works
// Best practice to use both for defense in depth
.authorizeRequests(requests -> requests
.anyRequest().permitAll()
)
.httpBasic(withDefaults())
.build();
}
@Bean
public static InMemoryUserDetailsManager userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails rob = userBuilder.username("rob").password("rob").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build();
return new InMemoryUserDetailsManager(rob, admin);
}
}
}