如何在ASPNET.Core Web应用程序中使用CORS标头发送HTTP 4xx-5xx响应?

时间:2017-11-10 14:29:51

标签: c# asp.net asp.net-core cors asp.net-core-2.0

我有一个标准的ASP.NET Core 2 Web App充当REST / WebApi。对于我的一个端点,当用户提供错误的搜索/过滤器查询字符串参数时,我返回HTTP 400

适用于POSTMAN。但是,当我尝试使用我的SPA应用程序测试时(实际上现在正在跨域并因此执行CORS请求),我在Chrome中出现了故障。

对返回HTTP 200响应的端点执行CORS请求时,一切正常。

看起来我的错误处理没有考虑CORS的东西(即没有添加任何CORS头文件)并且不包括那些。

我猜测我弄乱了响应有效负载管道的东西。

问:有没有办法纠正在自定义错误处理中返回任何CORS标头信息而不对标头进行硬编码,而是使用在Configure/ConfigureServices方法中设置的标头内容Startup.cs }吗

Pseduo代码..

public void ConfigureServices(IServiceCollection services)
{
    ... snip ...

    services.AddMvcCore()
        .AddAuthorization()
        .AddFormatterMappings()
        .AddJsonFormatters(options =>
        {
            options.ContractResolver = new CamelCasePropertyNamesContractResolver();
            options.Formatting = Formatting.Indented;
            options.DateFormatHandling = DateFormatHandling.IsoDateFormat;
            options.NullValueHandling = NullValueHandling.Ignore;
            options.Converters.Add(new StringEnumConverter());
        })
        .AddCors(); // REF: https://docs.microsoft.com/en-us/aspnet/core/security/cors#setting-up-cors

    ... snip ...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ... snip ...

    app.UseExceptionHandler(options => options.Run(async httpContext => await ExceptionResponseAsync(httpContext, true)));

    app.UseCors(builder => builder//.WithOrigins("http://localhost:52383", "http://localhost:49497")
                                .AllowAnyOrigin()
                                .AllowAnyHeader()
                                .AllowAnyMethod());

    ... snip ...
}

private static async Task ExceptionResponseAsync(HttpContext httpContext, bool isDevelopmentEnvironment)
{
    var exceptionFeature = httpContext.Features.Get<IExceptionHandlerPathFeature>();
    if (exceptionFeature == null)
    {
        // An unknow and unhandled exception occured. So this is like a fallback.
        exceptionFeature = new ExceptionHandlerFeature
        {
            Error = new Exception("An unhandled and unexpected error has occured. Ro-roh :~(.")
        };
    }

    await ConvertExceptionToJsonResponseAsyn(exceptionFeature,
                                                httpContext.Response, 
                                                isDevelopmentEnvironment);
}

private static Task ConvertExceptionToJsonResponseAsyn(IExceptionHandlerPathFeature exceptionFeature,
    HttpResponse response,
    bool isDevelopmentEnvironment)
{
    if (exceptionFeature == null)
    {
        throw new ArgumentNullException(nameof(exceptionFeature));
    }

    if (response == null)
    {
        throw new ArgumentNullException(nameof(response));
    }

    var exception = exceptionFeature.Error;
    var includeStackTrace = false;
    var statusCode = HttpStatusCode.InternalServerError;
    var error = new ApiError();

    if (exception is ValidationException)
    {
        statusCode = HttpStatusCode.BadRequest;
        foreach(var validationError in ((ValidationException)exception).Errors)
        {
            error.AddError(validationError.PropertyName, validationError.ErrorMessage);
        }
    }
    else
    {
        // Final fallback.
        includeStackTrace = true;
        error.AddError(exception.Message);
    }

    if (includeStackTrace &&
        isDevelopmentEnvironment)
    {
        error.StackTrace = exception.StackTrace;
    }

    var json = JsonConvert.SerializeObject(error, JsonSerializerSettings);
    response.StatusCode = (int)statusCode;
    response.ContentType = JsonContentType;
    // response.Headers.Add("Access-Control-Allow-Origin", "*"); <-- Don't want to hard code this.
    return response.WriteAsync(json);
}

干杯!

1 个答案:

答案 0 :(得分:4)

ExceptionHandler中间件中,Response在传递到您自己的中间件函数之前已被清除,如source中所示:

try
{
    await _next(context);
}
catch (Exception ex)
{
    // ...
    context.Response.Clear();

    // ...
    await _options.ExceptionHandler(context);

    // ..
}

当然,这意味着可能已经针对CORS设置的任何响应头也是being cleared

以下代码插入到一般CORS系统中,我相信看起来确实满足了您可以使用ConfigureServices的配置的要求:

var corsService = httpContext.RequestServices.GetService<ICorsService>();
var corsPolicyProvider = httpContext.RequestServices.GetService<ICorsPolicyProvider>();
var corsPolicy = await corsPolicyProvider.GetPolicyAsync(httpContext, null);

corsService.ApplyResult(
    corsService.EvaluatePolicy(httpContext, corsPolicy),
    httpContext.Response);

GetPolicyAsync将策略名称作为第二个参数 - 如果为null(如我的示例所示),则将使用默认策略(如果已设置)。

我没有在代码示例中包含空值检查或任何内容,以便将其集中注意力,但这种方法适用于我构建的测试项目。

此方法受Microsoft.AspNetCore.Mvc.Cors中CorsAuthorizationFilter源代码的严重影响。

编辑:您没有在示例代码中使用命名策略,但可以使用以下命令切换到一个:

.AddCors(corsOptions => corsOptions.AddPolicy(
    "Default",
    corsPolicyBuilder => corsPolicyBuilder
        .AllowAnyOrigin()
        .AllowAnyHeader()
        .AllowAnyMethod()));

这使用了AddPolicy - 我在评论中提到了AddDefaultPolicy,但看起来这不在当前版本中,因此尚未提供。通过上述更改,您可以像这样调用UseCors

app.UseCors("Default");

最后的更改是在您的异常处理代码中更新到以下内容:

await corsPolicyProvider.GetPolicyAsync(httpContext, "Default");

你最好使用某种const字符串,特别是因为它可能都是从同一个文件运行的。这里的主要变化是不再尝试使用默认的命名策略,因为我正在查看GitHub上尚未发布的当前源代码版本。