处理对 ASP.NET MVC 操作的 CORS 预检请求

Posted

技术标签:

【中文标题】处理对 ASP.NET MVC 操作的 CORS 预检请求【英文标题】:Handling CORS Preflight requests to ASP.NET MVC actions 【发布时间】:2012-11-17 10:39:36 【问题描述】:

我正在尝试对 ASP.NET MVC 控制器操作执行跨域 POST 请求。此控制器操作接受并使用各种参数。问题是当预检请求发生时,控制器动作实际上尝试执行 & 因为 OPTIONS 请求没有传递任何数据,控制器动作抛出 500 HTTP 错误。如果我删除使用参数的代码,或者参数本身,整个请求链就成功完成了。

如何实现的示例:

控制器动作

public ActionResult GetData(string data)

    return new JsonResult
    
        Data = data.ToUpper(),
        JsonRequestBehavior = JsonRequestBehavior.AllowGet
    ;

客户端代码

<script type="text/javascript">
        $(function () 
            $("#button-request").click(function () 
                var ajaxConfig = 
                    dataType: "json",
                    url: "http://localhost:8100/host/getdata",
                    contentType: 'application/json',
                    data: JSON.stringify( data: "A string of data" ),
                    type: "POST",
                    success: function (result) 
                        alert(result);
                    ,
                    error: function (jqXHR, textStatus, errorThrown) 
                        alert('Error: Status: ' + textStatus + ', Message: ' + errorThrown);
                    
                ;

                $.ajax(ajaxConfig);
            );
        );
    </script>

现在,无论何时发生预检请求,它都会返回一个 500 HTTP 代码,因为“data”参数为空,因为 OPTIONS 请求没有传递任何值。

服务器应用程序已在我的本地 IIS 的 8100 端口上设置,运行客户端代码的页面设置在 8200 端口上以模拟跨域调用。

我还为主机(在 8100 上)配置了以下标头:

Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Origin: http://localhost:8200

我发现的一种解决方法是检查执行操作的 HTTP 方法,如果是 OPTIONS 请求,则只返回空白内容,否则执行操作代码。像这样:

public ActionResult GetData(string data)

    if (Request.HttpMethod == "OPTIONS") 
        return new ContentResult();
     else 
        return new JsonResult
        
            Data = data.ToUpper(),
            JsonRequestBehavior = JsonRequestBehavior.AllowGet
        ;
    

但我觉得这种方法很笨拙。我曾考虑将这种逻辑添加到 Attribute,但即使这样也意味着装饰将使用 CORS 调用的每个操作。

有没有更优雅的解决方案来让这个功能发挥作用?

【问题讨论】:

为我工作。为什么微软的一切都如此细致入微? 【参考方案1】:

所以我找到了一个可行的解决方案。对于每个请求,我检查它是否是 CORS 请求以及请求是否带有 OPTIONS 动词,表明它是预检请求。如果是,我只是发回一个空响应(当然只包含在 IIS 中配置的标头),从而否定控制器操作执行。

然后,如果客户端确认允许根据预检返回的标头执行请求,则执行实际的 POST 并执行控制器操作。我的代码示例:

protected void Application_BeginRequest()

    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") 
        Response.Flush();
    

如前所述,这对我有用,但如果有人知道更好的方法,或者我当前的实现中存在任何缺陷,我将不胜感激。

【讨论】:

Cors 确实很有趣。我遇到了tinyurl.com/nxz65ac 的问题,我在我的WebAPI 上实现了。文件上传很小时工作正常,我可以上传倍数就好,但是当我选择一个更大的文件说大约 50MB 并尝试上传时,我得到“没有'Access-Control-Allow-Origin'标头存在于请求的资源。”在铬。事件虽然如果我选择多个文件,较小的文件可以正常上传,但大文件会使浏览器停止。我会创建一个新问题,但我不知道如何标记像你这样解决了令人痛苦的 Cors 问题的用户。有人知道怎么做吗? 这不是 CORS 问题,但奇怪的是 Chrome 说它是。 WebAPI 中有两个文件大小限制区域,我必须同时设置:***.com/a/17324840 这进入 global.asax 并且是您可以挂钩的应用程序级事件之一。 这对我来说不太奏效,但调试和检查使我做出了这个小改动。添加特定的标头并确保明确 命名 Access-Control-Allow-Headers(星号不够用)。 if (Request.Headers.AllKeys.Contains("Origin") &amp;&amp; Request.HttpMethod == "OPTIONS") Response.Headers.Add("Access-Control-Allow-Origin", "*"); Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, Session"); Response.Flush(); 标头键不区分大小写,我遇到过 Chrome 中的 jQuery 会为 HTTP 发送“Origin”而为 HTTPS 发送“origin”的情况。不过,jQuery 和 Chrome 的这种组合不太可能做到这一点。我已更新您的答案以不区分大小写检查以解决此差异。【参考方案2】:

接受的答案就像一个魅力,但我发现请求实际上正在传递给控制器​​。我收到了200 状态代码,但响应正文包含大量 html,但控制器有异常。因此,与其使用Response.Flush(),不如使用Response.End(),它确实会停止请求的执行。这种替代解决方案如下所示:

编辑:修正了原始答案中的一个错字。

protected void Application_BeginRequest()

    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") 
        Response.End();
    

【讨论】:

你把这个函数放在哪里了? 看起来它在应用程序根文件夹的 Global.asax.cs 中。【参考方案3】:

以下是我使用 ASP.Net Web Api 处理预检/CORS 问题的方法。我只是将 Microsoft.AspNet.WebApi.Cors Nuget 包添加到我的 Web 项目中。然后在我的 WebApiConfig.cs 文件中我添加了这一行:

config.EnableCors(new ApplicationCorsPolicy());

并创建了一个自定义 PolicyProvider 类

public class ApplicationCorsPolicy : Attribute, ICorsPolicyProvider

    public async Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    
        var corsRequestContext = request.GetCorsRequestContext();
        var originRequested = corsRequestContext.Origin;

        if (await IsOriginFromAPaidCustomer(originRequested))
        
            // Grant CORS request
            var policy = new CorsPolicy
            
                AllowAnyHeader = true,
                AllowAnyMethod = true
            ;
            policy.Origins.Add(originRequested);
            return policy;
        
        // Reject CORS request
        return null;
    

    private async Task<bool> IsOriginFromAPaidCustomer(string originRequested)
    
        // Do database look up here to determine if origin should be allowed.
        // In my application I have a table that has a list of domains that are
        // allowed to make API requests to my service. This is validated here.
        return true;
    

看,Cors 框架允许您添加自己的逻辑来确定允许哪些来源等。如果您将 REST API 公开给外部世界以及可以访问的人员(来源)列表,这将非常有用您的站点处于数据库等受控环境中。现在,如果您只是允许所有来源(在所有情况下这可能不是一个好主意),您可以在 WebApiConfig.cs 中执行此操作以全局启用 CORS:

config.EnableCors();

就像 WebApi 中的过滤器和处理程序一样,您也可以向控制器添加类或方法级别的注释,如下所示:

[EnableCors("*, *, *, *")]

请注意,EnableCors 属性有一个接受以下参数的构造函数

    允许的来源列表 允许的请求标头列表 允许的 HTTP 方法列表 允许的响应标头列表

您可以在每个控制器/端点静态指定允许访问什么资源的人员。

2016 年 6 月 24 日更新: 我应该提到我的 Web.config 中有以下内容。看起来这些可能不是每个人的默认设置。

<system.webServer>
    <handlers>
        <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
        <remove name="OPTIONSVerbHandler" />
        <remove name="TRACEVerbHandler" />
        <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
        </handlers>
</system.webServer>

来源:Microsoft

【讨论】:

【参考方案4】:

这些答案都不适合我,但以下 webconfig 设置可以。我的两个关键设置是将Access-Control-Allow-Headers 设置为Content-Type 并注释掉删除OPTIONSVerbHandler 的行:

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"></modules>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type" />
      </customHeaders>
    </httpProtocol>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <!--<remove name="OPTIONSVerbHandler" />-->
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>

【讨论】:

需要注意的是,上面的代码对每个来源都启用了 CORS。通常,您想尽可能地尝试限制来源。您是否在 WebApiConfig.cs 文件中尝试过 config.EnableCors()【参考方案5】:

扩展 Carl 的答案,我将他的代码插入到我的 OWIN 管道中:

app.Use((context, next) =>

     if (context.Request.Headers.Any(k => k.Key.Contains("Origin")) && context.Request.Method == "OPTIONS")
     
         context.Response.StatusCode = 200;
         return context.Response.WriteAsync("handled");
     

     return next.Invoke();
);

只需将其添加到 Startup.cs 中 IAppBuilder 的开头(或注册 WebAPI 之前的任何位置)

【讨论】:

【参考方案6】:

这可能是一个红鲱鱼。我最近让 CORS 工作正常,没有跳过你正在做的任何麻烦。

这是使用 Thinktecture.IdentityModel nuget 包的组合完成的,更重要的是...删除所有对 WebDAV 的引用。这包括从 IIS 中删除 webdav 模块,并确保您的 web 配置中有以下几行:

<system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="WebDAVModule" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>
    <handlers>
      <remove name="WebDAV" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
  <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

然后您可以使用 thinktecture 从 Global.asax 使用如下静态类配置 CORS:

public class CorsConfig

    public static void RegisterCors(HttpConfiguration httpConfiguration)
    
        var corsConfig = new WebApiCorsConfiguration();
        corsConfig.RegisterGlobal(httpConfiguration);

        corsConfig.ForAllResources().AllowAllOriginsAllMethodsAndAllRequestHeaders();
    

来源:http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/

【讨论】:

我在研究期间一直在研究 Thinktecture,但这似乎是一件很简单的事情,我想找到一种无需第三方库的方式来实现它。不过,感谢您花时间发布您的答案:) 没问题,有时候自己做会更有益也更有趣:) 谢谢!您的回答为我指明了正确的方向。 CORS 为我工作,但我不小心从我的 web.config 中删除了 ExtensionlessUrlHandler 东西,这破坏了我的 CORS(我应该删除无异常的东西......)

以上是关于处理对 ASP.NET MVC 操作的 CORS 预检请求的主要内容,如果未能解决你的问题,请参考以下文章

ASP .NET MVC5 中的 CORS

CORS 不工作 ASP.Net MVC5

ASP.NET 5 MVC 6 CORS vue.js 请求不允许

在 ASP.NET 核心 MVC 项目上启用 CORS

在 ASP.Net 5 / Core 中启用跨域资源共享 (CORS)

CORS:使用 ASP.NET 4.5.1 MVC 的 Rest API