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:
parent
450d5fb7f4
commit
372bfb46a6
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
spring.thymeleaf.prefix=classpath:/templates/
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user