André Gasser
André Gasser

Reputation: 1105

Return custom http error message from Spring Security filter

I am building a REST API that uses Spring Security (and it's filter chain) to authenticate the user via JWT. Now, if such a JWT is missing, expired or similar, I would like to return a nicely formatted error message to the API consumer, instead of the default whitelabel error response. The API error messages returned out of the Spring Security filter should look identical to the ones returned in case of business logic failure.

In case of business logic failure, my Spring REST controllers return error messages that are formatted like this (via @RestControllerAdvice):

Content-Type: application/json

{
  "code": "VOUCHER_NOT_FOUND",
  "message": "The specified voucher code was not found.",
  "timestamp": "2020-09-06T21:22:23.015Z"
}

I understand that if an error happens in the Spring Security filter chain, controllers will never be reached, so I have to return an HTTP error message out of the security filter. I've tried to do this like this:

public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

    @Override
    protected final void doFilterInternal(final HttpServletRequest request,
            final HttpServletResponse response, final FilterChain chain)
            throws IOException, ServletException {
        try {
            // Perform various auth checks here
            // Throw JwtAuthorizationException if a check fails
            chain.doFilter(request, response);
        } catch (JwtAuthorizationFailedException e) {
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setHeader("Content-Type", "application/json");  // does not work
            response.getWriter().write("{ \"Simple\": \"Test\" }");
        }

}

The problem is, that the resulting error message I get, always sets a different Content-Type header (with charset=ISO-8859-1 added):

Content-Type: application/json;charset=ISO-8859-1

{
  "Simple": "Test"
}

I would like to streamline this and make it consistent. So the question is, how can I make sure, only

Content-Type: application/json

is returned from the security filter? I've tried numerous options, like

response.setHeader("Content-Type", "application/json");

or

response.setContentType(MediaType.APPLICATION_JSON_VALUE);

but all of them did not work. Any ideas?

Upvotes: 3

Views: 3248

Answers (1)

user731136
user731136

Reputation:

The problem in this case comes from getWriter() method:

getWriter()

This one includes the default character encoding into the Response that will be returned and, as you can see in the next picture, it provokes the "additional information" inside the returned Content-Type.

getContentType

When Spring serialize the response, uses "getter methods" and, as you can see, getContentType includes the current charset. That is the reason you see that one besides desired Content-Type value.

Even if you try to set charset with a null value, it won't work because the method will detect you are using a Writer and it won't be changed (see next picture)

setCharacterEncoding

However, there is a way to achieve what you want:

} catch (JwtAuthorizationFailedException e) {
  response.setStatus(HttpStatus.UNAUTHORIZED.value());
  response.getOutputStream().print("{ \"Simple\": \"Test\" }");
  response.setContentType(MediaType.APPLICATION_JSON_VALUE);
}

Use getOutputStream().print instead of getWriter().write

Upvotes: 4

Related Questions