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 :).
Advertisement
Answer
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 byAccessDeniedException
andAuthenticationEntryPoint
.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
implementation
import 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
Implementation
import 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
implementation
package 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
implementation
import 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(); } }