I have a Spring Boot app using Jersey as the JAX-RS implementation. This is my security configuration:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired TokenAuthenticationProvider tokenAuthenticationProvider;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(tokenAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new AuthenticationTokenFilter(), BasicAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests()
.antMatchers("/dataHub/**")
.authenticated();
}
}
What I want to be able to do is to have a way to catch the Exceptions thrown by my TokenAuthenticationProvider and convert them into a standardized JSON format that we have agreed upon. Is there a way to do this? I tried messing around with adding a custom AuthenticationFailureHandler, but couldn't get that to work.
WebSecurityConfigurerAdapter appraoch
The HttpSecurity
class has a method called exceptionHandling which can be used to override the default behavior. The following sample presents how the response message can be customized.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// your custom configuration goes here
.exceptionHandling()
.authenticationEntryPoint((request, response, e) -> {
String json = String.format("{\"message\": \"%s\"}", e.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
});
}
@ControllerAdvice appraoch - Why it doesn't work in this case
At first I thought about @ControllerAdvice
that catches authentication exceptions for the entire application.
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
@ControllerAdvice
public class AuthExceptionHandler {
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(AuthenticationException.class)
@ResponseBody
public String handleAuthenticationException(AuthenticationException e) {
return String.format("{\"message\": \"%s\"}", e.getMessage());
}
}
In the example above, the JSON is built manually, but you can simply return a POJO which will be mapped into JSON just like from a regular REST controller. Since Spring 4.3 you can also use @RestControllerAdvice, which is a combination of @ControllerAdvice
and @ResponseBody
.
However, this approach doesn't work because the exception is thrown by the AbstractSecurityInterceptor
and handled by ExceptionTranslationFilter before any controller is reached.