MVC - 当 content-type 是自定义的(不是 application/json)时接受 JSON

MVC - Accept JSON when content-type is custom (not application/json)

我正在尝试为我的网站实施 Content-Security-Policy-Report-Only Header。 为此,我需要一个接受浏览器 POST-Request 的控制器 - 它将以 JSON 的形式发送有关违规的数据。但是,此请求似乎将 Content-Type 指定为 application/csp-report 而不是 application/json (旁注:到底为什么??)。

这显然导致 Spring 拒绝请求 - @RequestBody 的使用似乎使 spring 只接受 Content-Type [=16= 的请求]. 即使我专门将 @RequestMapping 注释的值 consumes 设置为 application/csp-report 它仍然不接受请求。

我已经使用过滤器将请求包装成 HttpServletRequestWrapper - 这似乎是修改请求行为的常用方法。 我已经覆盖了所有这些方法:getContentType()getHeader(String)getHeaders(String) 到 return "application/json"。 但是请求还是没有通过。

根据有关 @RequestBody 注释的文档,

The body of the request is passed through an HttpMessageConverter to resolve the method argument depending on the content type of the request

这意味着 Spring Framework 定义了一个专门的 API 用于定义某些 MediaTypes 在用作 REST 端点的参数时如何转换为某些 Java 类型。

Here 是一篇很好的文章,展示了这些功能。

有大量内置 Spring 转换器,如果您的媒体格式可以映射到它们各自的媒体格式,您就可以配置和使用它们。特别针对您的情况,您应该查看 spring.converter.json 包中可用的转换器之一。在最简单的情况下,让它工作应该很简单:

HttpMessageConverter converter = new <JsonConverterOfYourChoice>(JsonMapper);

converter.getSupportedMediaTypes().add(new MediaType("application", "csp-report"));

然后将此类转换器注册到 spring 的配置中。

其他可用的转换器类型包括:

  • StringHttpMessageConverter
  • ObjectToStringHttpMessageConverter(如果您已经配置 Spring 的 ConversionService 以从字符串中读取您想要的类型)
  • ByteArrayHttpMessageConverter
  • FormHttpMessageConverter
  • 生活在spring.converter.xml包中的各种基于XML的转换器
  • AllEncompassingFormHttpMessageConverter(转换器建立在 Form 转换器之上,并且能够处理 XML 和 JSON)
  • ProtobufHttpMessageConverter
  • Atom/RSS 个供稿消息转换器。

最后,如果上述 none 不适合您,您可以制作并注册自己的 HttpMessageConverter 实现。

构建成一个完整的片段,这段代码对我有用,可以在 Spring Boot 中添加 application/csp-report 作为 JSON 消息转换器(使用 Jackson):

@Bean
public WebMvcConfigurer webMvcConfigurer() {
  return new WebMvcConfigurerAdapter() {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
      super.extendMessageConverters(converters);

      final List<MediaType> cspReportMediaTypes =
          Collections.singletonList(new MediaType("application", "csp-report"));

      final HttpMessageConverter<Object> cspReportConverter =
          new MappingJackson2HttpMessageConverter() {
            @Override
            public List<MediaType> getSupportedMediaTypes() {
              return cspReportMediaTypes;
            }
          };

      converters.add(cspReportConverter);
    }
  };
}

其他答案帮助我弄清楚 @ResponseBody 发生了什么。

CSP 报告仍然相对较新,在浏览器中还不是很标准化:

The CSP2 spec states that CSP report requests need to use a content-type header of application/csp-report. In my sample, I observed four different content-type headers:

application/x-www-form-urlencoded
application/json
application/csp-report
application/json; charset=UTF-8

What to Expect When Expecting Content Security Policy Reports

因此,要接受任何类型的内容,另一种方法是直接从 HttpServletRequest 读取输入流,而不是使用带有消息转换器的 @ReponseBody

例如,在 Kotlin 中:

@PostMapping(path = [CSP_REPORT_URL])
fun reportFrontendError(
    request: HttpServletRequest
): ResponseEntity<Void> {
    val body = request.inputStream.bufferedReader().use { it.readText() }

    // ...

    return ResponseEntity(HttpStatus.OK)
}