BAEL-5533: Spring security exceptions
This commit is contained in:
		
							parent
							
								
									908387dbfb
								
							
						
					
					
						commit
						3816e3371a
					
				| @ -0,0 +1,13 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler; | ||||||
|  | 
 | ||||||
|  | import org.springframework.boot.SpringApplication; | ||||||
|  | import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||||
|  | 
 | ||||||
|  | @SpringBootApplication | ||||||
|  | public class Application { | ||||||
|  | 
 | ||||||
|  |     public static void main(String[] args) { | ||||||
|  |         SpringApplication.run(Application.class, args); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,25 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler.controller; | ||||||
|  | 
 | ||||||
|  | import org.springframework.http.MediaType; | ||||||
|  | import org.springframework.http.ResponseEntity; | ||||||
|  | import org.springframework.web.bind.annotation.PostMapping; | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
|  | import org.springframework.web.bind.annotation.RestController; | ||||||
|  | 
 | ||||||
|  | import com.baeldung.global.exceptionhandler.handler.RestResponse; | ||||||
|  | 
 | ||||||
|  | @RestController | ||||||
|  | @RequestMapping | ||||||
|  | public class LoginController { | ||||||
|  | 
 | ||||||
|  |     @PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE) | ||||||
|  |     public ResponseEntity<RestResponse> login() { | ||||||
|  |         return ResponseEntity.ok(new RestResponse("Success")); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @PostMapping(value = "/login-handler", produces = MediaType.APPLICATION_JSON_VALUE) | ||||||
|  |     public ResponseEntity<RestResponse> loginWithExceptionHandler() { | ||||||
|  |         return ResponseEntity.ok(new RestResponse("Success")); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,22 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler.handler; | ||||||
|  | 
 | ||||||
|  | import org.springframework.http.HttpStatus; | ||||||
|  | import org.springframework.http.ResponseEntity; | ||||||
|  | import org.springframework.security.core.AuthenticationException; | ||||||
|  | import org.springframework.web.bind.annotation.ControllerAdvice; | ||||||
|  | import org.springframework.web.bind.annotation.ExceptionHandler; | ||||||
|  | import org.springframework.web.bind.annotation.ResponseBody; | ||||||
|  | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; | ||||||
|  | 
 | ||||||
|  | @ControllerAdvice | ||||||
|  | public class DefaultExceptionHandler extends ResponseEntityExceptionHandler { | ||||||
|  | 
 | ||||||
|  |     @ExceptionHandler({ AuthenticationException.class }) | ||||||
|  |     @ResponseBody | ||||||
|  |     public ResponseEntity<RestError> handleAuthenticationException(Exception ex) { | ||||||
|  | 
 | ||||||
|  |         RestError re = new RestError(HttpStatus.UNAUTHORIZED.toString(), "Authentication failed at controller advice"); | ||||||
|  |         return ResponseEntity.status(HttpStatus.UNAUTHORIZED) | ||||||
|  |             .body(re); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler.handler; | ||||||
|  | 
 | ||||||
|  | public class RestError { | ||||||
|  | 
 | ||||||
|  |     String errorCode; | ||||||
|  |     String errorMessage; | ||||||
|  | 
 | ||||||
|  |     public RestError(String errorCode, String errorMessage) { | ||||||
|  |         super(); | ||||||
|  |         this.errorCode = errorCode; | ||||||
|  |         this.errorMessage = errorMessage; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String getErrorCode() { | ||||||
|  |         return errorCode; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setErrorCode(String errorCode) { | ||||||
|  |         this.errorCode = errorCode; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String getErrorMessage() { | ||||||
|  |         return errorMessage; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setErrorMessage(String errorMessage) { | ||||||
|  |         this.errorMessage = errorMessage; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,20 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler.handler; | ||||||
|  | 
 | ||||||
|  | public class RestResponse { | ||||||
|  | 
 | ||||||
|  |     String message; | ||||||
|  | 
 | ||||||
|  |     public RestResponse(String message) { | ||||||
|  |         super(); | ||||||
|  |         this.message = message; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String getMessage() { | ||||||
|  |         return message; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setMessage(String message) { | ||||||
|  |         this.message = message; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,35 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler.security; | ||||||
|  | 
 | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.OutputStream; | ||||||
|  | 
 | ||||||
|  | import javax.servlet.ServletException; | ||||||
|  | import javax.servlet.http.HttpServletRequest; | ||||||
|  | import javax.servlet.http.HttpServletResponse; | ||||||
|  | 
 | ||||||
|  | import org.springframework.http.HttpStatus; | ||||||
|  | import org.springframework.http.MediaType; | ||||||
|  | import org.springframework.security.core.AuthenticationException; | ||||||
|  | import org.springframework.security.web.AuthenticationEntryPoint; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  | 
 | ||||||
|  | import com.baeldung.global.exceptionhandler.handler.RestError; | ||||||
|  | import com.fasterxml.jackson.databind.ObjectMapper; | ||||||
|  | 
 | ||||||
|  | @Component("customAuthenticationEntryPoint") | ||||||
|  | public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { | ||||||
|  | 
 | ||||||
|  |         RestError re = new RestError(HttpStatus.UNAUTHORIZED.toString(), "Authentication failed"); | ||||||
|  |          | ||||||
|  |         response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||||||
|  |         response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); | ||||||
|  |         OutputStream responseStream = response.getOutputStream(); | ||||||
|  |         ObjectMapper mapper = new ObjectMapper(); | ||||||
|  |         mapper.writeValue(responseStream, re); | ||||||
|  |         responseStream.flush(); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,58 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler.security; | ||||||
|  | 
 | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.beans.factory.annotation.Qualifier; | ||||||
|  | import org.springframework.context.annotation.Bean; | ||||||
|  | import org.springframework.context.annotation.Configuration; | ||||||
|  | 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.provisioning.InMemoryUserDetailsManager; | ||||||
|  | import org.springframework.security.web.AuthenticationEntryPoint; | ||||||
|  | 
 | ||||||
|  | @Configuration | ||||||
|  | @EnableWebSecurity | ||||||
|  | public class CustomSecurityConfig extends WebSecurityConfigurerAdapter { | ||||||
|  | 
 | ||||||
|  |     @Autowired | ||||||
|  |     @Qualifier("customAuthenticationEntryPoint") | ||||||
|  |     AuthenticationEntryPoint authEntryPoint; | ||||||
|  | 
 | ||||||
|  |     @Bean | ||||||
|  |     public UserDetailsService userDetailsService() { | ||||||
|  |         UserDetails admin = User.withUsername("admin") | ||||||
|  |             .password("password") | ||||||
|  |             .roles("ADMIN") | ||||||
|  |             .build(); | ||||||
|  |         InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager(); | ||||||
|  |         userDetailsManager.createUser(admin); | ||||||
|  |         return userDetailsManager; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void configure(HttpSecurity http) throws Exception { | ||||||
|  |         http.requestMatchers() | ||||||
|  |             .antMatchers("/login") | ||||||
|  |             .and() | ||||||
|  |             .authorizeRequests() | ||||||
|  |             .anyRequest() | ||||||
|  |             .hasRole("ADMIN") | ||||||
|  |             .and() | ||||||
|  |             .httpBasic() | ||||||
|  |             .and() | ||||||
|  |             .exceptionHandling() | ||||||
|  |             .authenticationEntryPoint(authEntryPoint); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void configure(AuthenticationManagerBuilder auth) throws Exception { | ||||||
|  |         auth.inMemoryAuthentication() | ||||||
|  |             .withUser("admin") | ||||||
|  |             .password("password") | ||||||
|  |             .roles("ADMIN"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,27 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler.security; | ||||||
|  | 
 | ||||||
|  | import java.io.IOException; | ||||||
|  | 
 | ||||||
|  | import javax.servlet.ServletException; | ||||||
|  | import javax.servlet.http.HttpServletRequest; | ||||||
|  | import javax.servlet.http.HttpServletResponse; | ||||||
|  | 
 | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.beans.factory.annotation.Qualifier; | ||||||
|  | import org.springframework.security.core.AuthenticationException; | ||||||
|  | import org.springframework.security.web.AuthenticationEntryPoint; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  | import org.springframework.web.servlet.HandlerExceptionResolver; | ||||||
|  | 
 | ||||||
|  | @Component("delegatedAuthenticationEntryPoint") | ||||||
|  | public class DelegatedAuthenticationEntryPoint implements AuthenticationEntryPoint { | ||||||
|  | 
 | ||||||
|  |     @Autowired | ||||||
|  |     @Qualifier("handlerExceptionResolver") | ||||||
|  |     private HandlerExceptionResolver resolver; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { | ||||||
|  |         resolver.resolveException(request, response, null, authException); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,44 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler.security; | ||||||
|  | 
 | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.beans.factory.annotation.Qualifier; | ||||||
|  | import org.springframework.context.annotation.Configuration; | ||||||
|  | import org.springframework.core.annotation.Order; | ||||||
|  | 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.web.AuthenticationEntryPoint; | ||||||
|  | 
 | ||||||
|  | @Configuration | ||||||
|  | @EnableWebSecurity | ||||||
|  | @Order(101) | ||||||
|  | public class DelegatedSecurityConfig extends WebSecurityConfigurerAdapter { | ||||||
|  | 
 | ||||||
|  |     @Autowired | ||||||
|  |     @Qualifier("delegatedAuthenticationEntryPoint") | ||||||
|  |     AuthenticationEntryPoint authEntryPoint; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void configure(HttpSecurity http) throws Exception { | ||||||
|  |         http.requestMatchers() | ||||||
|  |             .antMatchers("/login-handler") | ||||||
|  |             .and() | ||||||
|  |             .authorizeRequests() | ||||||
|  |             .anyRequest() | ||||||
|  |             .hasRole("ADMIN") | ||||||
|  |             .and() | ||||||
|  |             .httpBasic() | ||||||
|  |             .and() | ||||||
|  |             .exceptionHandling() | ||||||
|  |             .authenticationEntryPoint(authEntryPoint); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void configure(AuthenticationManagerBuilder auth) throws Exception { | ||||||
|  |         auth.inMemoryAuthentication() | ||||||
|  |             .withUser("admin") | ||||||
|  |             .password("password") | ||||||
|  |             .roles("ADMIN"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,52 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler; | ||||||
|  | 
 | ||||||
|  | import static org.hamcrest.Matchers.is; | ||||||
|  | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; | ||||||
|  | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | ||||||
|  | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||
|  | 
 | ||||||
|  | import org.junit.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.context.annotation.Import; | ||||||
|  | import org.springframework.http.HttpStatus; | ||||||
|  | import org.springframework.http.MediaType; | ||||||
|  | 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.global.exceptionhandler.controller.LoginController; | ||||||
|  | import com.baeldung.global.exceptionhandler.handler.RestError; | ||||||
|  | import com.baeldung.global.exceptionhandler.security.CustomAuthenticationEntryPoint; | ||||||
|  | import com.baeldung.global.exceptionhandler.security.DelegatedAuthenticationEntryPoint; | ||||||
|  | import com.baeldung.global.exceptionhandler.security.DelegatedSecurityConfig; | ||||||
|  | 
 | ||||||
|  | @RunWith(SpringRunner.class) | ||||||
|  | @WebMvcTest(DelegatedSecurityConfig.class) | ||||||
|  | @Import({ LoginController.class, CustomAuthenticationEntryPoint.class, DelegatedAuthenticationEntryPoint.class }) | ||||||
|  | public class DelegatedSecurityConfigUnitTest { | ||||||
|  | 
 | ||||||
|  |     @Autowired | ||||||
|  |     private MockMvc mvc; | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @WithMockUser(username = "admin", roles = { "ADMIN" }) | ||||||
|  |     public void whenUserAccessLogin_shouldSucceed() throws Exception { | ||||||
|  |         mvc.perform(formLogin("/login-handler").user("username", "admin") | ||||||
|  |             .password("password", "password") | ||||||
|  |             .acceptMediaType(MediaType.APPLICATION_JSON)) | ||||||
|  |             .andExpect(status().isOk()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void whenUserAccessWithWrongCredentialsWithDelegatedEntryPoint_shouldFail() throws Exception { | ||||||
|  |         RestError re = new RestError(HttpStatus.UNAUTHORIZED.toString(), "Authentication failed at controller advice"); | ||||||
|  |         mvc.perform(formLogin("/login-handler").user("username", "admin") | ||||||
|  |             .password("password", "wrong") | ||||||
|  |             .acceptMediaType(MediaType.APPLICATION_JSON)) | ||||||
|  |             .andExpect(status().isUnauthorized()) | ||||||
|  |             .andExpect(jsonPath("$.errorMessage", is(re.getErrorMessage()))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,54 @@ | |||||||
|  | package com.baeldung.global.exceptionhandler; | ||||||
|  | 
 | ||||||
|  | import static org.hamcrest.Matchers.is; | ||||||
|  | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; | ||||||
|  | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | ||||||
|  | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||
|  | 
 | ||||||
|  | import org.junit.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.context.annotation.Import; | ||||||
|  | import org.springframework.http.HttpStatus; | ||||||
|  | import org.springframework.http.MediaType; | ||||||
|  | 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.global.exceptionhandler.controller.LoginController; | ||||||
|  | import com.baeldung.global.exceptionhandler.handler.RestError; | ||||||
|  | import com.baeldung.global.exceptionhandler.security.CustomAuthenticationEntryPoint; | ||||||
|  | import com.baeldung.global.exceptionhandler.security.CustomSecurityConfig; | ||||||
|  | import com.baeldung.global.exceptionhandler.security.DelegatedAuthenticationEntryPoint; | ||||||
|  | 
 | ||||||
|  | @RunWith(SpringRunner.class) | ||||||
|  | @WebMvcTest(CustomSecurityConfig.class) | ||||||
|  | @Import({LoginController.class, CustomAuthenticationEntryPoint.class, DelegatedAuthenticationEntryPoint.class}) | ||||||
|  | public class SecurityConfigUnitTest { | ||||||
|  | 
 | ||||||
|  |     @Autowired | ||||||
|  |     private MockMvc mvc; | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @WithMockUser(username = "admin", roles = { "ADMIN" }) | ||||||
|  |     public void whenUserAccessLogin_shouldSucceed() throws Exception { | ||||||
|  |         mvc.perform(formLogin("/login").user("username", "admin") | ||||||
|  |             .password("password", "password") | ||||||
|  |             .acceptMediaType(MediaType.APPLICATION_JSON)) | ||||||
|  |             .andExpect(status().isOk()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void whenUserAccessWithWrongCredentialsWithDelegatedEntryPoint_shouldFail() throws Exception { | ||||||
|  |         RestError re = new RestError(HttpStatus.UNAUTHORIZED.toString(), "Authentication failed"); | ||||||
|  |         mvc.perform(formLogin("/login").user("username", "admin") | ||||||
|  |             .password("password", "wrong") | ||||||
|  |             .acceptMediaType(MediaType.APPLICATION_JSON)) | ||||||
|  |             .andExpect(status().isUnauthorized()) | ||||||
|  |             .andExpect(jsonPath("$.errorMessage", is(re.getErrorMessage()))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  | 
 | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user