diff --git a/spring-security-modules/pom.xml b/spring-security-modules/pom.xml index 2bd6d23058..83412d2252 100644 --- a/spring-security-modules/pom.xml +++ b/spring-security-modules/pom.xml @@ -32,6 +32,7 @@ spring-security-web-boot-1 spring-security-web-boot-2 spring-security-web-boot-3 + spring-security-web-boot-4 spring-security-web-digest-auth spring-security-web-login spring-security-web-login-2 diff --git a/spring-security-modules/spring-security-web-boot-4/README.md b/spring-security-modules/spring-security-web-boot-4/README.md new file mode 100644 index 0000000000..0856315682 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/README.md @@ -0,0 +1,10 @@ +## Spring Boot Security MVC + +This module contains articles about Spring Security with Spring MVC in Boot applications + +### The Course +The "REST With Spring" Classes: http://github.learnspringsecurity.com + +### Relevant Articles: + +- More articles: [[<-- prev]](/spring-security-modules/spring-security-web-boot-3) diff --git a/spring-security-modules/spring-security-web-boot-4/pom.xml b/spring-security-modules/spring-security-web-boot-4/pom.xml new file mode 100644 index 0000000000..8dd56e1de2 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + spring-security-web-boot-4 + 0.0.1-SNAPSHOT + spring-security-web-boot-4 + jar + Spring Security MVC Boot - 4 + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/SecurityFilterChainApplication.java b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/SecurityFilterChainApplication.java new file mode 100644 index 0000000000..86f98b651b --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/SecurityFilterChainApplication.java @@ -0,0 +1,14 @@ +package com.baeldung.securityfilterchain; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +@SpringBootApplication +@EnableWebMvc +public class SecurityFilterChainApplication { + + public static void main(String[] args) { + SpringApplication.run(SecurityFilterChainApplication.class, args); + } +} diff --git a/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/SecurityConfig.java b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/SecurityConfig.java new file mode 100644 index 0000000000..4d3bec2ad2 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/SecurityConfig.java @@ -0,0 +1,50 @@ +package com.baeldung.securityfilterchain.configuration; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +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.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; + +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) +public class SecurityConfig { + + @Value("${spring.security.debug:false}") + boolean securityDebug; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf() + .disable() + .authorizeRequests() + .antMatchers(HttpMethod.DELETE) + .hasRole("ADMIN") + .antMatchers("/admin/**") + .hasAnyRole("ADMIN") + .antMatchers("/user/**") + .hasAnyRole("USER", "ADMIN") + .antMatchers("/login/**") + .anonymous() + .anyRequest() + .authenticated() + .and() + .httpBasic() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + return http.build(); + } + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.debug(securityDebug) + .ignoring() + .antMatchers("/css/**", "/js/**", "/img/**", "/lib/**", "/favicon.ico"); + } +} diff --git a/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/UserDetailServiceConfig.java b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/UserDetailServiceConfig.java new file mode 100644 index 0000000000..6a614e888b --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/configuration/UserDetailServiceConfig.java @@ -0,0 +1,31 @@ +package com.baeldung.securityfilterchain.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; + +@Configuration +public class UserDetailServiceConfig { + + @Bean + public UserDetailsService userDetailsService(BCryptPasswordEncoder bCryptPasswordEncoder) { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + manager.createUser(User.withUsername("user") + .password(bCryptPasswordEncoder.encode("userPass")) + .roles("USER") + .build()); + manager.createUser(User.withUsername("admin") + .password(bCryptPasswordEncoder.encode("adminPass")) + .roles("ADMIN", "USER") + .build()); + return manager; + } + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/controller/ResourceController.java b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/controller/ResourceController.java new file mode 100644 index 0000000000..e01d4ae9b3 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/main/java/com/baeldung/securityfilterchain/controller/ResourceController.java @@ -0,0 +1,34 @@ +package com.baeldung.securityfilterchain.controller; + +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ResourceController { + @GetMapping("/login") + public String loginEndpoint() { + return "Login!"; + } + + @GetMapping("/admin") + public String adminEndpoint() { + return "Admin!"; + } + + @GetMapping("/user") + public String userEndpoint() { + return "User!"; + } + + @GetMapping("/all") + public String allRolesEndpoint() { + return "All Roles!"; + } + + @DeleteMapping("/delete") + public String deleteEndpoint(@RequestBody String s) { + return "I am deleting " + s; + } +} diff --git a/spring-security-modules/spring-security-web-boot-4/src/test/java/com/baeldung/securityfilterchain/SecurityFilterChainIntegrationTest.java b/spring-security-modules/spring-security-web-boot-4/src/test/java/com/baeldung/securityfilterchain/SecurityFilterChainIntegrationTest.java new file mode 100644 index 0000000000..e94b1b2f12 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/test/java/com/baeldung/securityfilterchain/SecurityFilterChainIntegrationTest.java @@ -0,0 +1,94 @@ +package com.baeldung.securityfilterchain; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.test.context.support.WithAnonymousUser; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@SpringBootTest(classes = SecurityFilterChainApplication.class) +public class SecurityFilterChainIntegrationTest { + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @BeforeEach + public void setup() { + mvc = MockMvcBuilders.webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @WithUserDetails(value = "admin") + public void whenAdminAccessUserEndpoint_thenOk() throws Exception { + mvc.perform(get("/user")) + .andExpect(status().isOk()); + } + + @Test + @WithUserDetails(value = "admin") + public void whenAdminAccessAdminSecuredEndpoint_thenIsOk() throws Exception { + mvc.perform(get("/admin")) + .andExpect(status().isOk()); + } + + @Test + @WithUserDetails(value = "admin") + public void whenAdminAccessDeleteSecuredEndpoint_thenIsOk() throws Exception { + mvc.perform(delete("/delete").content("{}")) + .andExpect(status().isOk()); + } + + @Test + @WithAnonymousUser + public void whenAnonymousAccessLogin_thenOk() throws Exception { + mvc.perform(get("/login")) + .andExpect(status().isOk()); + } + + @Test + @WithAnonymousUser + public void whenAnonymousAccessRestrictedEndpoint_thenIsUnauthorized() throws Exception { + mvc.perform(get("/all")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithUserDetails() + public void whenUserAccessUserSecuredEndpoint_thenOk() throws Exception { + mvc.perform(get("/user")) + .andExpect(status().isOk()); + } + + @Test + @WithUserDetails() + public void whenUserAccessRestrictedEndpoint_thenOk() throws Exception { + mvc.perform(get("/all")) + .andExpect(status().isOk()); + } + + @Test + @WithUserDetails() + public void whenUserAccessAdminSecuredEndpoint_thenIsForbidden() throws Exception { + mvc.perform(get("/admin")) + .andExpect(status().isForbidden()); + } + + @Test + @WithUserDetails() + public void whenUserAccessDeleteSecuredEndpoint_thenIsForbidden() throws Exception { + mvc.perform(delete("/delete")) + .andExpect(status().isForbidden()); + } +} diff --git a/spring-security-modules/spring-security-web-boot-4/src/test/resources/application.properties b/spring-security-modules/spring-security-web-boot-4/src/test/resources/application.properties new file mode 100644 index 0000000000..090ff54e92 --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/test/resources/application.properties @@ -0,0 +1 @@ +spring.security.debug=true \ No newline at end of file diff --git a/spring-security-modules/spring-security-web-boot-4/src/test/resources/logback-test.xml b/spring-security-modules/spring-security-web-boot-4/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..c5a4b0ab1c --- /dev/null +++ b/spring-security-modules/spring-security-web-boot-4/src/test/resources/logback-test.xml @@ -0,0 +1,12 @@ + + + + + [%d{ISO8601}]-[%thread] %-5level %logger - %msg%n + + + + + + + \ No newline at end of file