简体   繁体   中英

"415 Unsupported Media Type" for Content-Type "application/csp-report" in ASP.NET Core

I have a content security policy that causes Chrome to post a report, but the action that receives the report returns "415 Unsupported Media Type". I understand this is because the post has a Content-Type of "application/csp-report". How do I add this as a allowed content type in Core 3.1 (its basically just json).

Action

// https://anthonychu.ca/post/aspnet-core-csp/
[HttpPost][Consumes("application/csp-report")]
public IActionResult Report([FromBody] CspReportRequest request)
{
    return Ok();
}

Cut down version of model

public class CspReportRequest
{
    [JsonProperty(PropertyName = "csp-report")]
    public CspReport CspReport { get; set; }
}

public class CspReport
{
    [JsonProperty(PropertyName = "document-uri")]
    public string DocumentUri { get; set; }
}

The following example shows how to add support to the SystemTextJsonInputFormatter for handling additional media-types:

services.AddControllers(options =>
{
    var jsonInputFormatter = options.InputFormatters
        .OfType<SystemTextJsonInputFormatter>()
        .Single();

    jsonInputFormatter.SupportedMediaTypes.Add("application/csp-report");
});

This is a two-step process:

  1. Interrogate the configured list of input-formatters to find the SystemTextJsonInputFormatter .
  2. Add application/csp-report to its existing list of supported media-types ( application/json , text/json , and application/*+json ).

If you're using Json.NET instead of System.Text.Json , the approach is similar :

services.AddControllers(options =>
{
    var jsonInputFormatter = options.InputFormatters
        .OfType<NewtonsoftJsonInputFormatter>()
        .First();

    jsonInputFormatter.SupportedMediaTypes.Add("application/csp-report");
})

There are two small differences:

  1. The type is NewtonsoftJsonInputFormatter instead of SystemTextJsonInputFormatter .
  2. There are two instances of this type in the collection, so we target the first (see this answer for the specifics).

See Input Formatters in the ASP.NET Core docs to learn more about those.

I had the same problem last week and found an alternative solution using my own custom formatter :

using CspReportLogger.Models;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using System;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace CspReportLogger.Formatters
{
  public class CSPReportInputFormatter : TextInputFormatter
  {
    public CSPReportInputFormatter()
    {
      // Specify the custom media type.
      SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/csp-report"));
      SupportedEncodings.Add(Encoding.UTF8);
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding)
    {
      // Let ASP interrupt deserialization
      var cancellationToken = context.HttpContext.RequestAborted;

      // Avoid InvalidCastException, pull type from controller
      var modelType = context.ModelType;

      // Deserialize the body using our models and the JsonSerializer.
      var report = await JsonSerializer.DeserializeAsync(context.HttpContext.Request.Body, modelType, null, cancellationToken);
      return await InputFormatterResult.SuccessAsync(report);
    }
  }
}

Which has to be registered in Startup.cs of course:

    public void ConfigureServices(IServiceCollection services)
    {
      services.AddControllers(options =>
      {
        options.InputFormatters.Insert(0, new CSPReportInputFormatter());
      });
    }

I wish I had seen Kirk Larkin's solution earlier, as it obviously is more concise.

I suppose the custom formatter solution is helpful, if you want to accept body types that aren't valid json though.

I would like to add that the accepted solution did not work for me. (.NET Core 3.1) I have the exact same use case regarding the CSP reports. When trying to use NewtonSoft and modifying the InputFormatter NewtonsoftJsonInputFormatter to accept media header type application/csp-report , I would always get an exception saying the inputformatter could not be found (with or without .AddNewtonsoftJson(); )

I managed to solve the problem by doing the following:

services.AddControllers().AddNewtonsoftJson();
services.AddOptions<MvcOptions>()
      .PostConfigure<IOptions<JsonOptions>, IOptions<MvcNewtonsoftJsonOptions>, ArrayPool<char>, ObjectPoolProvider, ILoggerFactory>(
          (mvcOptions, jsonOpts, newtonJsonOpts, charPool, objectPoolProvider, loggerFactory) =>
          {
              var formatter = mvcOptions.InputFormatters.OfType<NewtonsoftJsonInputFormatter>().First(i => i.SupportedMediaTypes.Contains("application/json"));
              formatter.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/csp-report"));
              mvcOptions.InputFormatters.RemoveType<NewtonsoftJsonInputFormatter>();
              mvcOptions.InputFormatters.Add(formatter);
          });

My model and the controller action are the same as the ones posted in the question.

(I derived my solution from How to configure two JSON serializers and select the correct one based on the route )

Thanks to rm-code for this. I did have to make a few changes because I was getting a null value at:

var report = await JsonSerializer.DeserializeAsync(context.HttpContext.Request.Body, modelType, null, cancellationToken);

Here is what finally worked for me.

using Namespace.WebUI.Models;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using System.IO;
using System.Text;
using System.Threading.Tasks;

public class CSPReportInputFormatter : TextInputFormatter
{
    public CSPReportInputFormatter()
    {
        // Specify the custom media type.
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/csp-report"));
        SupportedEncodings.Add(Encoding.UTF8);
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding effectiveEncoding)
    {
        using var reader = new StreamReader(context.HttpContext.Request.Body);
        string responseString = await reader.ReadToEndAsync();

        var data = JsonConvert.DeserializeObject<CspReportRequest>(responseString);

        return await InputFormatterResult.SuccessAsync(data);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM