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

Posted

技术标签:

【中文标题】如何在 ASPNET.Core Web 应用程序中发送带有 CORS 标头的 HTTP 4xx-5xx 响应?【英文标题】:How to send an HTTP 4xx-5xx response with CORS headers in an ASPNET.Core web app? 【发布时间】:2018-04-23 19:32:49 【问题描述】:

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

与 POSTMAN 完美搭配。但是,当我尝试使用我的 SPA 应用程序(实际上是跨域并因此执行 CORS 请求)对此进行测试时,我在 Chrome 中遇到了故障。

向返回 HTTP 200 响应的端点发出 CORS 请求时,一切正常。

看起来我的错误处理没有考虑到 CORS 的东西(即不添加任何 CORS 标头)并且不包括在内。

我猜我搞砸了响应负载管道的东西。

问:有没有一种方法可以更正返回自定义错误处理中的任何 CORS 标头信息,而无需对标头进行硬编码,而是使用在 Startup.cs 中的 Configure/ConfigureServices 方法中设置的标头内容?强>

伪代码..

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】:

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 - 我在 cmets 中提到了AddDefaultPolicy,但看起来这不在当前版本中,因此尚不可用。通过上述更改,您可以像这样调用UseCors

app.UseCors("Default");

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

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

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

【讨论】:

哇哦!很棒的皮卡!好的,所以我尝试了corsPolicy = await corsPolicyProvider.GetPolicyAsync(httpContext, null); 位,但一直返回null(这会导致ApplyResult 崩溃。我没有手动设置任何策略名称,我认为这意味着代码默认为DefaultPolicyName ...因此将null 传递给GetPolicyAsync 应该使用默认名称...并从中解决? 看起来你最终是here,在那里你可以看到名称设置为null,而不是默认值。为了让它工作,看起来你可以在AddCors中使用AddDefaultPolicy而不是AddPolicy(假设你使用的是AddPolicy)。 嗯...我想如果我没有添加策略名称(实际上我没有).. 那么它会默认为默认策略名称吗?另外,我也找不到任何AddDefaultPolicy ....。 用代码更新了 OP。我实际上只是按照 MS 文档的说明进行操作:docs.microsoft.com/en-us/aspnet/core/security/… 有趣——CORS repo 中存在一个关于此的问题。参考:github.com/aspnet/CORS/issues/90 我刚刚也为该问题添加了更多信息。

以上是关于如何在 ASPNET.Core Web 应用程序中发送带有 CORS 标头的 HTTP 4xx-5xx 响应?的主要内容,如果未能解决你的问题,请参考以下文章

AspNet Core 2.2使用Mysql一些问题及解决方案

带有 spotify 身份验证回调 url 的 Aspnet Core MVC 应用程序重定向到 127.0.0.1

如何使用 Visual Studio 2017 在 ASPNET Core 中排除 wwwroot\lib

Swashbuckle.AspNet.Core:Swagger UI 显示空白页面 - 如何修复?

AspNet Core Identity,如何设置options.Cookie.SameSite?

AspNet Core SignalR - 在单声道上运行时的依赖性问题