首页 > 解决方案 > 从 Spring Security 过滤器返回自定义 http 错误消息

问题描述

我正在构建一个 REST API,它使用 Spring Security(及其过滤器链)通过 JWT 对用户进行身份验证。现在,如果这样的 JWT 丢失、过期或类似情况,我想向 API 使用者返回格式正确的错误消息,而不是默认的白标签错误响应。从 Spring Security 过滤器返回的 API 错误消息应该与业务逻辑失败时返回的错误消息相同。

如果业务逻辑失败,我的 Spring REST 控制器会返回格式如下的错误消息(通过@RestControllerAdvice):

Content-Type: application/json

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

我知道如果 Spring Security 过滤器链中发生错误,将永远无法到达控制器,因此我必须从安全过滤器中返回一条 HTTP 错误消息。我试过这样做:

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\" }");
        }

}

问题是,我得到的错误消息总是设置不同的Content-Type标题(charset=ISO-8859-1添加):

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

{
  "Simple": "Test"
}

我想简化这一点并使其保持一致。所以问题是,我怎样才能确定,只有

Content-Type: application/json

从安全过滤器返回?我尝试了很多选择,比如

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

或者

response.setContentType(MediaType.APPLICATION_JSON_VALUE);

但他们都没有工作。有任何想法吗?

标签: javaspring-bootrestspring-security

解决方案


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

获取作家()

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

获取内容类型

当 Spring 序列化响应时,使用“getter 方法”,并且如您所见,getContentType包括当前的charset. 这就是您看到除了期望值之外的原因Content-Type

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

设置字符编码

但是,有一种方法可以实现您想要的:

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

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


推荐阅读