Exception Handler for Spring Security Resource Server (#12085)

* Exception Handler implemented for Spring Security Resource Server

* Renamed test class name to solve PMD Failure

* Code formatting
This commit is contained in:
Pablo Aragonés López 2022-04-25 19:50:22 +02:00 committed by GitHub
parent 450d5fb7f4
commit 372bfb46a6
15 changed files with 306 additions and 0 deletions

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1 @@
spring.thymeleaf.prefix=classpath:/templates/

View File

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<div>Hello Admin!</div>
<button type="button" onclick="window.location.href='/logout'">Logout</button>
</html>

View File

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<div>You need permission to perform this action.</div>
<button type="button" onclick="window.location.href='/logout'">Logout</button>
</html>

View File

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<div>Ups! Wrong credentials</div>
<button type="button" onclick="window.location.href='/login'">Try Again</button>
</html>

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<div>Hello User!</div>
<button type="button" onclick="window.location.href='/secured'">Secured Page</button>
<button type="button" onclick="window.location.href='/logout'">Logout</button>
</html>

View File

@ -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"));
}
}