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))