Handle spring security authentication exceptions with @ExceptionHandler Handle spring security authentication exceptions with @ExceptionHandler spring spring

Handle spring security authentication exceptions with @ExceptionHandler


Ok, I tried as suggested writing the json myself from the AuthenticationEntryPoint and it works.

Just for testing I changed the AutenticationEntryPoint by removing response.sendError

@Component("restAuthenticationEntryPoint")public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {            response.setContentType("application/json");        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);        response.getOutputStream().println("{ \"error\": \"" + authenticationException.getMessage() + "\" }");    }}

In this way you can send custom json data along with the 401 unauthorized even if you are using Spring Security AuthenticationEntryPoint.

Obviously you would not build the json as I did for testing purposes but you would serialize some class instance.

In Spring Boot, you should add it to http.authenticationEntryPoint() part of SecurityConfiguration file.


This is a very interesting problem that Spring Security and Spring Web framework is not quite consistent in the way they handle the response. I believe it has to natively support error message handling with MessageConverter in a handy way.

I tried to find an elegant way to inject MessageConverter into Spring Security so that they could catch the exception and return them in a right format according to content negotiation. Still, my solution below is not elegant but at least make use of Spring code.

I assume you know how to include Jackson and JAXB library, otherwise there is no point to proceed. There are 3 Steps in total.

Step 1 - Create a standalone class, storing MessageConverters

This class plays no magic. It simply stores the message converters and a processor RequestResponseBodyMethodProcessor. The magic is inside that processor which will do all the job including content negotiation and converting the response body accordingly.

public class MessageProcessor { // Any name you like    // List of HttpMessageConverter    private List<HttpMessageConverter<?>> messageConverters;    // under org.springframework.web.servlet.mvc.method.annotation    private RequestResponseBodyMethodProcessor processor;    /**     * Below class name are copied from the framework.     * (And yes, they are hard-coded, too)     */    private static final boolean jaxb2Present =        ClassUtils.isPresent("javax.xml.bind.Binder", MessageProcessor.class.getClassLoader());    private static final boolean jackson2Present =        ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", MessageProcessor.class.getClassLoader()) &&        ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", MessageProcessor.class.getClassLoader());    private static final boolean gsonPresent =        ClassUtils.isPresent("com.google.gson.Gson", MessageProcessor.class.getClassLoader());    public MessageProcessor() {        this.messageConverters = new ArrayList<HttpMessageConverter<?>>();        this.messageConverters.add(new ByteArrayHttpMessageConverter());        this.messageConverters.add(new StringHttpMessageConverter());        this.messageConverters.add(new ResourceHttpMessageConverter());        this.messageConverters.add(new SourceHttpMessageConverter<Source>());        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());        if (jaxb2Present) {            this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());        }        if (jackson2Present) {            this.messageConverters.add(new MappingJackson2HttpMessageConverter());        }        else if (gsonPresent) {            this.messageConverters.add(new GsonHttpMessageConverter());        }        processor = new RequestResponseBodyMethodProcessor(this.messageConverters);    }    /**     * This method will convert the response body to the desire format.     */    public void handle(Object returnValue, HttpServletRequest request,        HttpServletResponse response) throws Exception {        ServletWebRequest nativeRequest = new ServletWebRequest(request, response);        processor.handleReturnValue(returnValue, null, new ModelAndViewContainer(), nativeRequest);    }    /**     * @return list of message converters     */    public List<HttpMessageConverter<?>> getMessageConverters() {        return messageConverters;    }}

Step 2 - Create AuthenticationEntryPoint

As in many tutorials, this class is essential to implement custom error handling.

public class CustomEntryPoint implements AuthenticationEntryPoint {    // The class from Step 1    private MessageProcessor processor;    public CustomEntryPoint() {        // It is up to you to decide when to instantiate        processor = new MessageProcessor();    }    @Override    public void commence(HttpServletRequest request,        HttpServletResponse response, AuthenticationException authException)        throws IOException, ServletException {        // This object is just like the model class,         // the processor will convert it to appropriate format in response body        CustomExceptionObject returnValue = new CustomExceptionObject();        try {            processor.handle(returnValue, request, response);        } catch (Exception e) {            throw new ServletException();        }    }}

Step 3 - Register the entry point

As mentioned, I do it with Java Config. I just show the relevant configuration here, there should be other configuration such as session stateless, etc.

@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {    @Override    protected void configure(HttpSecurity http) throws Exception {        http.exceptionHandling().authenticationEntryPoint(new CustomEntryPoint());    }}

Try with some authentication fail cases, remember the request header should include Accept : XXX and you should get the exception in JSON, XML or some other formats.


The best way I've found is to delegate the exception to the HandlerExceptionResolver

@Component("restAuthenticationEntryPoint")public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {    @Autowired    private HandlerExceptionResolver resolver;    @Override    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {        resolver.resolveException(request, response, null, exception);    }}

then you can use @ExceptionHandler to format the response the way you want.