使用多源 PUT 和 DELETE 请求时,如何解决 ASP.NET Web API CORS 预检问题?

How to solve ASP.NET Web API CORS Preflight issue when using PUT and DELETE requests with multiple origins?

我有一个 ASP.NET 网站 API 被三个不同的 SPA 调用。我正在为 Web API 使用 windows 身份验证。我最初尝试像这样在 Web.config 中配置 CORS:

<httpProtocol>
    <customHeaders>
        <add name="Access-Control-Allow-Origin" value="http://localhost:63342" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE" />
        <add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
        <add name="Access-Control-Allow-Credentials" value="true" />
    </customHeaders>
</httpProtocol>

这导致了这个预检问题:

Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin (...) is therefore not allowed access.

我通过在Global.asax.cs中添加以下方法解决了:

protected void Application_BeginRequest()
{
    if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
    {
        Response.Flush();
    }
}

这种方法非常适合单个 SPA。我想我可以去 Web.config 并像这样添加其他来源:

<add name="Access-Control-Allow-Origin" value="http://localhost:63342,http://localhost:63347,http://localhost:63345/>

但显然这是不允许的。这产生了以下错误:

The 'Access-Control-Allow-Origin' header contains multiple values (...), but only one is allowed. Origin (...) is therefore not allowed access.

所以为了尝试解决这个问题,我改变了我的方法,而是决定尝试在 WebAPIConfig.cs 上配置 CORS,在这样的注册方法中:

var cors = new EnableCorsAttribute("http://localhost:63342,http://localhost:63347,http://localhost:63345", "Origin, X-Requested-With, Content-Type, Accept", "GET, POST, PUT, DELETE");
cors.SupportsCredentials = true;
config.EnableCors(cors);

我认为这可行,但现在我在使用 PUT 和 DELETE 请求时再次遇到预检错误,我不知道如何解决这个问题。我调试了 Application_BeginRequest 方法,它仍在刷新 OPTIONS 请求,所以我不知道是什么导致了这个错误。有谁知道我该如何解决这个问题?

编辑:

预检错误打印:

使用 ICorsPolicyProvider 创建自定义属性,如下所示,以检查请求的来源是否被允许

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,AllowMultiple = false)]
    public class EnableCorsForAPIKeysAttribute :
      Attribute, ICorsPolicyProvider, IFilter
    {
        public async Task<CorsPolicy> GetCorsPolicyAsync(
          HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var corsRequestContext = request.GetCorsRequestContext();
            var originRequested = corsRequestContext.Origin;
            if (await IsValidOrigin(originRequested)) //Check if requested origin is valid or not
            {
                // Grant CORS request
                var policy = new CorsPolicy
                {
                    AllowAnyHeader = true,
                    AllowAnyMethod = true
                };
                policy.Origins.Add(originRequested);
                return policy;
            }
            else
            {
                // Reject CORS request
                return null;
            }
        }

        public bool AllowMultiple { get {return false;} }
    }

要使用它,请将其添加到您的 API 控制器

[EnableCorsForAPIKeys]
public class APIBaseController : ApiController
{
}

我能够通过进一步自定义 Global.asax.cs 中的 Application_BeginRequest 方法来解决我的问题,如下所示:

protected void Application_BeginRequest()
{
    if (Request.HttpMethod == "OPTIONS")
    {
        Response.StatusCode = (int)HttpStatusCode.OK;
        Response.AppendHeader("Access-Control-Allow-Origin", Request.Headers.GetValues("Origin")[0]);
        Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
        Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
        Response.AppendHeader("Access-Control-Allow-Credentials", "true");
        Response.End();
    }
}

此代码的作用是将缺失的 headers 添加到导致预检错误的 OPTIONS 响应(预检请求)中。由于我有不同的来源调用我的网站 API,我使用 Request.Headers.GetValues("Origin")[0]) 动态地设置响应中的来源。

在 WebApiConfig.cs 中,我仍然指定了不同的来源,但在 headers 和方法上使用了通配符,并将 SupportsCredentials 设置为 true,如下所示:

var cors = new EnableCorsAttribute("http://localhost:63342,http://localhost:63347,http://localhost:63345", "*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);

此外,如果您像我一样使用 AngularJS,则必须配置 $http 以使用凭据。这可以像这样全局配置:

angular
.module('Application')
.config(['$httpProvider',
    function config($httpProvider) {
        $httpProvider.defaults.withCredentials = true;
    }
]);

就是这样。这解决了我的问题。如果其他人仍有问题,我建议阅读以下出版物,这有助于我找到答案: