如何强制 Spring Boot 设置正确的 HTTP 状态代码来响应错误?

How to force Spring Boot to set correct HTTP status code to error response?

我制作了这个简单的 Spring 启动应用程序示例来展示我的问题 Spring 默认情况下启动处理错误的方式:

package com.pany.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}

这是 Spring 安全配置:

package com.pany.app;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").anonymous();
    }

}

这是 REST 控制器:

package com.pany.app;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;

@RestController
public class MyController {

    @GetMapping(path = "/", produces = { "application/json" })
    public void home() {
        throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "This is my custom 400 Bad Request message");
    }

}

现在,使用 Spring Boot 2.1.8.RELEASE,当调用 URL http://localhost:8080/ 时,我收到一个包含 500 代码的 HTTP 响应和以下 JSON 响应:

{
  "timestamp": "2020-09-14T07:24:22.061+0000",
  "status": 500,
  "error": "Internal Server Error",
  "message": "400 This is my custom 400 Bad Request message",
  "path": "/"
}

但是 HTTP 状态代码并不像预期的那样(500 而不是 400),以及 JSON 中的各个字段(“状态”、“错误”、“消息”具有意外的“代码” “在它前面)。

我也用 Spring Boot 2.3.3.RELEASE 测试过这个...现在它甚至不再像以前那样工作了!!

{
  "timestamp": "2020-09-14T07:34:23.154+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "message": "",
  "path": "/"
}

这一切都是通过ErrorControllerAbstractErrorControllerBasicErrorController等classes/interfaces处理的(都在ErrorMvcAutoConfiguration中设置)。显然有一个重定向到 URL /error,原始请求/and/or 响应被包装到其他东西中,例如我可以像这样调试我的请求: SecurityContextHolderAwareRequestWrapper[FirewalledRequest[org.apache.catalina.core.ApplicationHttpRequest@3751bea4]]

我发现我得到的 JSON 是通过一个名为 DefaultErrorAttributes 的 bean 生成的,它从键 "javax.servlet.error.status_code" 下的请求中提取 HTTP 状态代码。

我在那里读到:https://github.com/spring-projects/spring-boot/issues/4694#issuecomment-163597864(2015 年 12 月):

The problem's actually in BasicErrorController and is specific to HTML responses. BasicErrorController.error sets the response status using the javax.servlet.error.status_code attribute that's set by ErrorPageFilter, but BasicErrorController.errorHtml does not. This results in text/html error responses have a status of 200 OK.

事实上,在我的例子中,ErrorPageFilter 从未被调用过。

还在 Spring 引导项目上发现了这个问题:https://github.com/spring-projects/spring-boot/issues/20412 这与我的问题很相关,但仍然没有提供解决方案。

回到我的控制器是如何设计的(记住这只是一个极其简化的例子):如何“定制”Spring 引导以获得代码为 400 的 HTTP 响应和这个 JSON 作为回应:

{
  "timestamp": "2020-09-14T07:24:22.061+0000",
  "status": 400,
  "error": "Bad Request",
  "message": "This is my custom 400 Bad Request message",
  "path": "/"
}

谢谢。

进行自定义错误响应的最简单方法是添加控制器通知 class,它将捕获并处理异常。

@ControllerAdvice
public class ControllerAdvice {
    @ResponseBody
    @ExceptionHandler(value = HttpClientErrorException.class) {
    public ResponseEntity<ExceptionResponseBody> genericException(HttpClientErrorException exception) {
        // Build and return your response

您可以从异常中获取代码和消息以构建响应。您还可以添加更多处理程序来捕获和处理您想要的所有错误。

@Controlleradvice 的一个简单替代方法是自定义异常如下

@Slf4j
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException{

  public NotFoundException(String message) {
    super(message);
    log.warn(message);
  }

}

使用带有 @ResponseStatus 注释的所需 Http 代码。

并在需要时抛出此异常

throw new NotFoundException("Your message for not found goes here");