从Spring Security过滤器返回自定义的HTTP错误消息。

huangapple go评论89阅读模式
英文:

Return custom http error message from Spring Security filter

问题

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.setContentType("application/json"); // Set the Content-Type header
            response.setCharacterEncoding("UTF-8"); // Set the character encoding
            String errorMessage = "{ \"Simple\": \"Test\" }";
            response.getWriter().write(errorMessage);
        }
    }
}
英文:

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?

答案1

得分: 3

在这种情况下的问题来自于getWriter()方法:

从Spring Security过滤器返回自定义的HTTP错误消息。

这个问题在返回的Response中包含了默认的字符编码,正如你在下一个图片中看到的那样,它引发了在**返回的Content-Type**内的“附加信息”。

从Spring Security过滤器返回自定义的HTTP错误消息。

当Spring对响应进行序列化时,它使用“getter方法”,正如你所看到的,getContentType包括了当前的charset。这就是你看到它与期望的Content-Type值一起的原因。

即使你尝试使用null值设置charset,也不起作用,因为该方法会检测到你正在使用Writer,并且不会被更改(参见下一个图片

从Spring Security过滤器返回自定义的HTTP错误消息。

然而,有一种方法可以实现你想要的效果:

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

使用getOutputStream().print替代getWriter().write

英文:

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

从Spring Security过滤器返回自定义的HTTP错误消息。

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.

从Spring Security过滤器返回自定义的HTTP错误消息。

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)

从Spring Security过滤器返回自定义的HTTP错误消息。

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

huangapple
  • 本文由 发表于 2020年10月14日 15:40:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/64348699.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定