From fbf7d7f3bd4627b774b6598833aae987d04bf182 Mon Sep 17 00:00:00 2001 From: DIMITRIOS CHRISOS Date: Thu, 31 Oct 2024 15:06:47 +0200 Subject: [PATCH] Finished Section 21: Spring Security Basic Auth --- pom.xml | 8 ++++++ .../config/SpringSecConfig.java | 25 +++++++++++++++++ src/main/resources/application.properties | 5 ++++ .../controller/BeerControllerIT.java | 27 ++++++++++++++++++- .../controller/BeerControllerTest.java | 18 ++++++++++++- .../controller/CustomerControllerTest.java | 18 ++++++++++--- 6 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 src/main/java/guru/springframework/spring6restmvc/config/SpringSecConfig.java diff --git a/pom.xml b/pom.xml index 7e91db71e..4b2d52c44 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,10 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-security + com.mysql mysql-connector-j @@ -68,6 +72,10 @@ spring-boot-starter-test test + + org.springframework.security + spring-security-test + diff --git a/src/main/java/guru/springframework/spring6restmvc/config/SpringSecConfig.java b/src/main/java/guru/springframework/spring6restmvc/config/SpringSecConfig.java new file mode 100644 index 000000000..bfbc3b089 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/config/SpringSecConfig.java @@ -0,0 +1,25 @@ +package guru.springframework.spring6restmvc.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +/** + * Created by jt, Spring Framework Guru. + */ +@Configuration +public class SpringSecConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + + http.authorizeHttpRequests() + .anyRequest().authenticated() + .and().httpBasic(Customizer.withDefaults()) + .csrf().ignoringRequestMatchers("/api/**"); + return http.build(); + } + +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1d00f86d2..ff0cf7864 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,6 +2,11 @@ logging.level.guru.springframework=debug spring.flyway.enabled=false +spring.security.user.name=user1 +spring.security.user.password=password + +#logging.level.org.springframework.security=trace + #spring.jpa.properties.jakarta.persistence.schema-generation.scripts.action=drop-and-create #spring.jpa.properties.jakarta.persistence.schema-generation.scripts.create-source=metadata #spring.jpa.properties.jakarta.persistence.schema-generation.scripts.drop-target=drop-and-create.sql diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java index ff20011ba..f7613f955 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java @@ -16,6 +16,7 @@ import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; import org.springframework.test.annotation.Rollback; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -30,6 +31,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -55,7 +58,10 @@ class BeerControllerIT { @BeforeEach void setUp() { - mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); + + mockMvc = MockMvcBuilders.webAppContextSetup(wac) + .apply(springSecurity()) + .build(); } @Disabled // just for demo purposes @@ -68,6 +74,7 @@ void testUpdateBeerBadVersion() throws Exception { beerDTO.setBeerName("Updated Name"); MvcResult result = mockMvc.perform(put(BeerController.BEER_PATH_ID, beer.getId()) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beerDTO))) @@ -79,6 +86,7 @@ void testUpdateBeerBadVersion() throws Exception { beerDTO.setBeerName("Updated Name 2"); MvcResult result2 = mockMvc.perform(put(BeerController.BEER_PATH_ID, beer.getId()) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beerDTO))) @@ -91,6 +99,7 @@ void testUpdateBeerBadVersion() throws Exception { @Test void tesListBeersByStyleAndNameShowInventoryTruePage2() throws Exception { mockMvc.perform(get(BeerController.BEER_PATH) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .queryParam("beerName", "IPA") .queryParam("beerStyle", BeerStyle.IPA.name()) .queryParam("showInventory", "true") @@ -104,6 +113,7 @@ void tesListBeersByStyleAndNameShowInventoryTruePage2() throws Exception { @Test void tesListBeersByStyleAndNameShowInventoryTrue() throws Exception { mockMvc.perform(get(BeerController.BEER_PATH) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .queryParam("beerName", "IPA") .queryParam("beerStyle", BeerStyle.IPA.name()) .queryParam("showInventory", "true") @@ -116,6 +126,7 @@ void tesListBeersByStyleAndNameShowInventoryTrue() throws Exception { @Test void tesListBeersByStyleAndNameShowInventoryFalse() throws Exception { mockMvc.perform(get(BeerController.BEER_PATH) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .queryParam("beerName", "IPA") .queryParam("beerStyle", BeerStyle.IPA.name()) .queryParam("showInventory", "false") @@ -128,6 +139,7 @@ void tesListBeersByStyleAndNameShowInventoryFalse() throws Exception { @Test void tesListBeersByStyleAndName() throws Exception { mockMvc.perform(get(BeerController.BEER_PATH) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .queryParam("beerName", "IPA") .queryParam("beerStyle", BeerStyle.IPA.name()) .queryParam("pageSize", "800")) @@ -135,9 +147,20 @@ void tesListBeersByStyleAndName() throws Exception { .andExpect(jsonPath("$.content.size()", is(310))); } + @Test + void testNoAuth() throws Exception { + + //Test No Auth + mockMvc.perform(get(BeerController.BEER_PATH) + .queryParam("beerStyle", BeerStyle.IPA.name()) + .queryParam("pageSize", "800")) + .andExpect(status().isUnauthorized()); + } + @Test void tesListBeersByStyle() throws Exception { mockMvc.perform(get(BeerController.BEER_PATH) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .queryParam("beerStyle", BeerStyle.IPA.name()) .queryParam("pageSize", "800")) .andExpect(status().isOk()) @@ -147,6 +170,7 @@ void tesListBeersByStyle() throws Exception { @Test void tesListBeersByName() throws Exception { mockMvc.perform(get(BeerController.BEER_PATH) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .queryParam("beerName", "IPA") .queryParam("pageSize", "800")) .andExpect(status().isOk()) @@ -161,6 +185,7 @@ void testPatchBeerBadName() throws Exception { beerMap.put("beerName", "New Name 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); mockMvc.perform(patch(BeerController.BEER_PATH_ID, beer.getId()) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beerMap))) diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java index 6605de2e2..26aff0034 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java @@ -1,6 +1,7 @@ package guru.springframework.spring6restmvc.controller; import com.fasterxml.jackson.databind.ObjectMapper; +import guru.springframework.spring6restmvc.config.SpringSecConfig; import guru.springframework.spring6restmvc.model.BeerDTO; import guru.springframework.spring6restmvc.services.BeerService; import guru.springframework.spring6restmvc.services.BeerServiceImpl; @@ -11,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -25,10 +27,12 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(BeerController.class) +@Import(SpringSecConfig.class) class BeerControllerTest { @Autowired @@ -48,6 +52,9 @@ class BeerControllerTest { @Captor ArgumentCaptor beerArgumentCaptor; + public static final String USERNAME = "user1"; + public static final String PASSWORD = "password"; + @BeforeEach void setUp() { beerServiceImpl = new BeerServiceImpl(); @@ -61,6 +68,7 @@ void testPatchBeer() throws Exception { beerMap.put("beerName", "New Name"); mockMvc.perform(patch(BeerController.BEER_PATH_ID, beer.getId()) + .with(httpBasic(USERNAME, PASSWORD)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beerMap))) @@ -79,6 +87,7 @@ void testDeleteBeer() throws Exception { given(beerService.deleteById(any())).willReturn(true); mockMvc.perform(delete(BeerController.BEER_PATH_ID, beer.getId()) + .with(httpBasic(USERNAME, PASSWORD)) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); @@ -94,6 +103,7 @@ void testUpdateBeer() throws Exception { given(beerService.updateBeerById(any(), any())).willReturn(Optional.of(beer)); mockMvc.perform(put(BeerController.BEER_PATH_ID, beer.getId()) + .with(httpBasic(USERNAME, PASSWORD)) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beer))) @@ -109,6 +119,7 @@ void testUpdateBeerBlankName() throws Exception { given(beerService.updateBeerById(any(), any())).willReturn(Optional.of(beer)); mockMvc.perform(put(BeerController.BEER_PATH_ID, beer.getId()) + .with(httpBasic(USERNAME, PASSWORD)) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beer))) @@ -126,6 +137,7 @@ void testCreateNewBeer() throws Exception { given(beerService.saveNewBeer(any(BeerDTO.class))).willReturn(beerServiceImpl.listBeers(null, null, false, 1, 25).getContent().get(1)); mockMvc.perform(post(BeerController.BEER_PATH) + .with(httpBasic(USERNAME, PASSWORD)) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beer))) @@ -141,6 +153,7 @@ void testCreateBeerNullBeerName() throws Exception { given(beerService.saveNewBeer(any(BeerDTO.class))).willReturn(beerServiceImpl.listBeers(null, null, false, 1, 25).getContent().get(1)); MvcResult mvcResult = mockMvc.perform(post(BeerController.BEER_PATH) + .with(httpBasic(USERNAME, PASSWORD)) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beerDTO))) @@ -157,6 +170,7 @@ void testListBeers() throws Exception { .willReturn(beerServiceImpl.listBeers(null, null, false, null, null)); mockMvc.perform(get(BeerController.BEER_PATH) + .with(httpBasic(USERNAME, PASSWORD)) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) @@ -168,7 +182,8 @@ void getBeerByIdNotFound() throws Exception { given(beerService.getBeerById(any(UUID.class))).willReturn(Optional.empty()); - mockMvc.perform(get(BeerController.BEER_PATH_ID, UUID.randomUUID())) + mockMvc.perform(get(BeerController.BEER_PATH_ID, UUID.randomUUID()) + .with(httpBasic(USERNAME, PASSWORD))) .andExpect(status().isNotFound()); } @@ -179,6 +194,7 @@ void getBeerById() throws Exception { given(beerService.getBeerById(testBeer.getId())).willReturn(Optional.of(testBeer)); mockMvc.perform(get(BeerController.BEER_PATH_ID, testBeer.getId()) + .with(httpBasic(USERNAME, PASSWORD)) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java index 73e3e7f2c..5e2a2d8be 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java @@ -1,6 +1,7 @@ package guru.springframework.spring6restmvc.controller; import com.fasterxml.jackson.databind.ObjectMapper; +import guru.springframework.spring6restmvc.config.SpringSecConfig; import guru.springframework.spring6restmvc.model.CustomerDTO; import guru.springframework.spring6restmvc.services.CustomerService; import guru.springframework.spring6restmvc.services.CustomerServiceImpl; @@ -11,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -24,10 +26,12 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(CustomerController.class) +@Import(SpringSecConfig.class) class CustomerControllerTest { @MockBean @@ -59,7 +63,8 @@ void testPatchCustomer() throws Exception { Map customerMap = new HashMap<>(); customerMap.put("name", "New Name"); - mockMvc.perform(patch( CustomerController.CUSTOMER_PATH_ID, customer.getId()) + mockMvc.perform(patch(CustomerController.CUSTOMER_PATH_ID, customer.getId()) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(customerMap))) .andExpect(status().isNoContent()); @@ -79,6 +84,7 @@ void testDeleteCustomer() throws Exception { given(customerService.deleteCustomerById(any())).willReturn(true); mockMvc.perform(delete(CustomerController.CUSTOMER_PATH_ID, customer.getId()) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); @@ -95,6 +101,7 @@ void testUpdateCustomer() throws Exception { .build())); mockMvc.perform(put(CustomerController.CUSTOMER_PATH_ID, customer.getId()) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .content(objectMapper.writeValueAsString(customer)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) @@ -114,7 +121,9 @@ void testCreateCustomer() throws Exception { given(customerService.saveNewCustomer(any(CustomerDTO.class))) .willReturn(customerServiceImpl.getAllCustomers().get(1)); - mockMvc.perform(post(CustomerController.CUSTOMER_PATH).contentType(MediaType.APPLICATION_JSON) + mockMvc.perform(post(CustomerController.CUSTOMER_PATH) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) + .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(customer))) .andExpect(status().isCreated()) @@ -126,6 +135,7 @@ void listAllCustomers() throws Exception { given(customerService.getAllCustomers()).willReturn(customerServiceImpl.getAllCustomers()); mockMvc.perform(get(CustomerController.CUSTOMER_PATH) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) @@ -137,7 +147,8 @@ void getCustomerByIdNotFound() throws Exception { given(customerService.getCustomerById(any(UUID.class))).willReturn(Optional.empty()); - mockMvc.perform(get(CustomerController.CUSTOMER_PATH_ID, UUID.randomUUID())) + mockMvc.perform(get(CustomerController.CUSTOMER_PATH_ID, UUID.randomUUID()) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD))) .andExpect(status().isNotFound()); } @@ -148,6 +159,7 @@ void getCustomerById() throws Exception { given(customerService.getCustomerById(customer.getId())).willReturn(Optional.of(customer)); mockMvc.perform(get(CustomerController.CUSTOMER_PATH_ID, customer.getId()) + .with(httpBasic(BeerControllerTest.USERNAME, BeerControllerTest.PASSWORD)) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON))