diff --git a/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/AppInitializer.java b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/AppInitializer.java new file mode 100644 index 0000000000..43921f71a2 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/AppInitializer.java @@ -0,0 +1,11 @@ +package com.baeldung.exceptionhandler; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AppInitializer { + public static void main(String[] args) { + SpringApplication.run(AppInitializer.class, args); + } +} diff --git a/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/AccessDeniedController.java b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/AccessDeniedController.java new file mode 100644 index 0000000000..0973fe1ad3 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/AccessDeniedController.java @@ -0,0 +1,15 @@ +package com.baeldung.exceptionhandler.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/access-denied") +public class AccessDeniedController { + + @GetMapping + public String accessDenied() { + return "/denied.html"; + } +} diff --git a/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/CustomErrorController.java b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/CustomErrorController.java new file mode 100644 index 0000000000..8b65a623eb --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/CustomErrorController.java @@ -0,0 +1,15 @@ +package com.baeldung.exceptionhandler.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping +public class CustomErrorController { + + @GetMapping("/customError") + public String customErrorController() { + return "/error"; + } +} diff --git a/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/HomeController.java b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/HomeController.java new file mode 100644 index 0000000000..d64bf7b5f3 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/HomeController.java @@ -0,0 +1,15 @@ +package com.baeldung.exceptionhandler.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/home") +public class HomeController { + + @GetMapping + public String home() { + return "/index.html"; + } +} diff --git a/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/SecuredResourceController.java b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/SecuredResourceController.java new file mode 100644 index 0000000000..a057570e29 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/controller/SecuredResourceController.java @@ -0,0 +1,15 @@ +package com.baeldung.exceptionhandler.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/secured") +public class SecuredResourceController { + + @GetMapping + public String secureResource() { + return "/admin.html"; + } +} diff --git a/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/CustomAccessDeniedHandler.java b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/CustomAccessDeniedHandler.java new file mode 100644 index 0000000000..a3d6aca9be --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/CustomAccessDeniedHandler.java @@ -0,0 +1,17 @@ +package com.baeldung.exceptionhandler.security; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; + +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exc) throws IOException { + response.sendRedirect("/access-denied"); + } +} diff --git a/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/CustomAuthenticationFailureHandler.java b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/CustomAuthenticationFailureHandler.java new file mode 100644 index 0000000000..281f9d5289 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/CustomAuthenticationFailureHandler.java @@ -0,0 +1,17 @@ +package com.baeldung.exceptionhandler.security; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; + +public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException { + httpServletResponse.sendRedirect("/customError"); + } +} diff --git a/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/CustomAuthenticationSuccessHandler.java b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/CustomAuthenticationSuccessHandler.java new file mode 100644 index 0000000000..62cbdf8873 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/CustomAuthenticationSuccessHandler.java @@ -0,0 +1,31 @@ +package com.baeldung.exceptionhandler.security; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + @Override + public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { + + HttpSession session = httpServletRequest.getSession(); + User authUser = (User) SecurityContextHolder.getContext() + .getAuthentication() + .getPrincipal(); + session.setAttribute("username", authUser.getUsername()); + session.setAttribute("authorities", authentication.getAuthorities()); + + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + + httpServletResponse.sendRedirect("/home"); + } +} diff --git a/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/SecurityConfig.java b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/SecurityConfig.java new file mode 100644 index 0000000000..71ded0f131 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/java/com/baeldung/exceptionhandler/security/SecurityConfig.java @@ -0,0 +1,98 @@ +package com.baeldung.exceptionhandler.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +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.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Bean + public UserDetailsService userDetailsService() { + + UserDetails user = User.withUsername("user") + .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode) + .password("password") + .roles("USER") + .build(); + + UserDetails admin = User.withUsername("admin") + .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode) + .password("password") + .roles("ADMIN") + .build(); + + InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager(); + + userDetailsManager.createUser(user); + userDetailsManager.createUser(admin); + + return userDetailsManager; + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("user") + .password("{noop}password") + .roles("USER") + .and() + .withUser("admin") + .password("{noop}password") + .roles("ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf() + .disable() + .httpBasic() + .disable() + .authorizeRequests() + .antMatchers("/login") + .permitAll() + .antMatchers("/customError") + .permitAll() + .antMatchers("/access-denied") + .permitAll() + .antMatchers("/secured") + .hasRole("ADMIN") + .anyRequest() + .authenticated() + .and() + .formLogin() + .failureHandler(authenticationFailureHandler()) + .successHandler(authenticationSuccessHandler()) + .and() + .exceptionHandling() + .accessDeniedHandler(accessDeniedHandler()) + .and() + .logout(); + } + + @Bean + public AuthenticationFailureHandler authenticationFailureHandler() { + return new CustomAuthenticationFailureHandler(); + } + + @Bean + public AuthenticationSuccessHandler authenticationSuccessHandler() { + return new CustomAuthenticationSuccessHandler(); + } + + @Bean + public AccessDeniedHandler accessDeniedHandler() { + return new CustomAccessDeniedHandler(); + } + +} diff --git a/spring-security-modules/spring-security-core/src/main/resources/application.properties b/spring-security-modules/spring-security-core/src/main/resources/application.properties new file mode 100644 index 0000000000..9d154c9cc0 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.thymeleaf.prefix=classpath:/templates/ \ No newline at end of file diff --git a/spring-security-modules/spring-security-core/src/main/resources/templates/admin.html b/spring-security-modules/spring-security-core/src/main/resources/templates/admin.html new file mode 100644 index 0000000000..d7f7ec232a --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/resources/templates/admin.html @@ -0,0 +1,5 @@ + + +
Hello Admin!
+ + diff --git a/spring-security-modules/spring-security-core/src/main/resources/templates/denied.html b/spring-security-modules/spring-security-core/src/main/resources/templates/denied.html new file mode 100644 index 0000000000..b7a8e09309 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/resources/templates/denied.html @@ -0,0 +1,5 @@ + + +
You need permission to perform this action.
+ + diff --git a/spring-security-modules/spring-security-core/src/main/resources/templates/error.html b/spring-security-modules/spring-security-core/src/main/resources/templates/error.html new file mode 100644 index 0000000000..ce74f05d99 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/resources/templates/error.html @@ -0,0 +1,5 @@ + + +
Ups! Wrong credentials
+ + diff --git a/spring-security-modules/spring-security-core/src/main/resources/templates/index.html b/spring-security-modules/spring-security-core/src/main/resources/templates/index.html new file mode 100644 index 0000000000..34d070b37a --- /dev/null +++ b/spring-security-modules/spring-security-core/src/main/resources/templates/index.html @@ -0,0 +1,6 @@ + + +
Hello User!
+ + + diff --git a/spring-security-modules/spring-security-core/src/test/java/com/baeldung/exceptionhandler/SecurityConfigUnitTest.java b/spring-security-modules/spring-security-core/src/test/java/com/baeldung/exceptionhandler/SecurityConfigUnitTest.java new file mode 100644 index 0000000000..ad35d575a5 --- /dev/null +++ b/spring-security-modules/spring-security-core/src/test/java/com/baeldung/exceptionhandler/SecurityConfigUnitTest.java @@ -0,0 +1,50 @@ +package com.baeldung.exceptionhandler; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.baeldung.exceptionhandler.security.SecurityConfig; + +@RunWith(SpringRunner.class) +@WebMvcTest(SecurityConfig.class) +class SecurityConfigUnitTest { + @Autowired + private MockMvc mvc; + + @Test + void whenUserAccessLogin_shouldSucceed() throws Exception { + mvc.perform(get("/login")) + .andExpect(status().isOk()); + } + + @Test + void whenUserAccessWithWrongCredentials_shouldRedirectToCustomErrorPage() throws Exception { + mvc.perform(formLogin("/login").user("username", "wrong") + .password("password", "credentials")) + .andExpect(redirectedUrl("/customError")); + } + + @Test + void whenUserAccessWithCorrectCredentials_shouldRedirectToHome() throws Exception { + mvc.perform(formLogin("/login").user("username", "user") + .password("password", "password")) + .andExpect(redirectedUrl("/home")); + } + + @Test + @WithMockUser(username = "user", roles = { "USER" }) + void whenUserAccessToSecuredPageWithoutUserRole_shouldRedirectToDeniedPage() throws Exception { + mvc.perform(get("/secured")) + .andExpect(redirectedUrl("/access-denied")); + } +} \ No newline at end of file