How to handle UsernameNotFoundException ?
In spring security when username not found the UserDetailsService implementation throws a UsernameNotFoundException. For example like this:
@Override
@Transactional
public UserDetails loadUserByUsername(java.lang.String username) throws UsernameNotFoundException {
logger.info("Load user by username: {}", username);
User user = userRepository.findUserByUsername(username).orElseThrow(
() -> new UsernameNotFoundException("User Not Found with -> username or email: " + username));
return UserPrinciple.build(user);
}
I would like to build a custom "User not found REST response". How should I catch/handle this exception? I have implemented a handler method in the WebSecurityConfigurerAdapter implementation the handler:
private static void handleException(HttpServletRequest req, HttpServletResponse rsp, AuthenticationException e)
throws IOException {
PrintWriter writer = rsp.getWriter();
writer.println(new ObjectMapper().writeValueAsString(new AuthResponse("", null, null, null, null,
"Authentication failed.", false)));
rsp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
But this method should wait for an AuthenticationException exception which, and during runtime the type of the exception is java.lang.NullPointerException so I'm not able to cast or retrieve the the initial UsernameNotFoundException.
Any advice would be appreciated. Many many regards :).
Security layer comes before anything in the controllers and @ControllerAdvice.
Hence @ControllerAdvice isn't an option since UsernameNotFoundException which is a subclass of AuthenticationException is thrown during authenticaton, making your exception handlers in @ControllerAdvice unreachable.
You can only use @ControllerAdvice and ResponseEntityExceptionHandler if you are throwing UsernameNotFoundException inside controller or any others beans referenced from the controllers.
Here is my suggestion - that you implement AuthenticationFailureHandler and use it with AuthenticationFilter that you are using for your security configuration.
Spring boot security comes with about 4 handler interfaces for security related issues
AccessDeniedHandler - this handles issues like when a user not having required roles.AuthenticationEntryPoint - this handles issues like when a user tries to access a resource without appropriate authentication elements.
AuthenticationFailureHandler - this handles issues like when a user is not found(i.e. UsernameNotFoundException) or other exceptions thrown inside authentication provider. In fact, this handles other authentication exceptions that are not handled by AccessDeniedException and AuthenticationEntryPoint.
AuthenticationSuccessHandler - this helps to do stuff like redirection after a user is successfully authenticated.
See the following example snippets for the implementation of all the 4 interfaces. Please customize these to your taste.
AccessDeniedHandler implementationimport com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
Map<String,Object> response = new HashMap<>();
response.put("status","34");
response.put("message","unauthorized api access");
//httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(out,response);
//mapper.writeValue(out, response);
out.flush();
}
}
AuthenticationEntryPoint Implementationimport com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
Map<String,Object> response = new HashMap<>();
response.put("status","34");
response.put("message","unauthorized access");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(out, response);
out.flush();
}
}
AuthenticationFailureHandler implementationpackage com.ibiller.webservices.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
@Component
public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler
{
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse httpServletResponse,
AuthenticationException ex) throws IOException, ServletException
{
Map<String,Object> response = new HashMap<>();
response.put("status","34");
response.put("message","unauthorized access");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(out, response);
out.flush();
}
}
AuthenticationSuccessHandler implementationimport org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class RestSuccessHandler implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
Set<String> roles =
AuthorityUtils.authorityListToSet(authentication.getAuthorities());
if (roles.contains("ROLE_ADMIN")) {
//do something
}
}
}
This is the Security configuration that extends WebSecurityConfigurerAdapter that connects everything together.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/v1/**"),new AntPathRequestMatcher("/admin/**")
);
AuthenticationProvider provider;
public SecurityConfiguration(final AuthenticationProvider authenticationProvider) {
super();
this.provider=authenticationProvider;
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
@Override
public void configure(final WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers("/info/**");//url that will be ignored
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.authenticationProvider(provider)
.addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/v1/**").hasRole("API")
.antMatchers("/admin/**").hasAnyRole("SUPER_ADMIN","ADMIN")
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
@Bean
AuthenticationFilter authenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
@Bean
RestAccessDeniedHandler accessDeniedHandler() {
return new RestAccessDeniedHandler();
}
@Bean
RestAuthenticationEntryPoint authenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}
@Bean
RestAuthenticationFailureHandler authenticationFailureHandler(){
return new RestAuthenticationFailureHandler();
}
@Bean
RestSuccessHandler successHandler(){
return new RestSuccessHandler();
}
}
I don't know the structure of your project, but a usual solution in this case is using @ControllerAdvice mechanism (separated class or in controller):
@ControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(value = UsernameNotFoundException.class)
public ResponseEntity handle(final UsernameNotFoundException exception) {
...//set headers, response attributes and response body
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With