带有 Windows 身份验证的 WebAPI CORS - 允许匿名 OPTIONS 请求

Posted

技术标签:

【中文标题】带有 Windows 身份验证的 WebAPI CORS - 允许匿名 OPTIONS 请求【英文标题】:WebAPI CORS with Windows Authentication - allow Anonymous OPTIONS request 【发布时间】:2015-02-09 10:38:12 【问题描述】:

我有一个使用 Windows 身份验证运行的 WebAPI 2 REST 服务。它与网站分开托管,因此我使用 ASP.NET CORS NuGet 包启用了 CORS。我的客户网站正在使用 AngularJS。

到目前为止,这是我所经历的:

    我没有设置 withCredentials,因此 CORS 请求返回 401。通过将 withCredentials 添加到我的 $httpProvider 配置中解决。 接下来,我使用通配符来源设置了 EnableCorsAttribute,这在使用凭据时是不允许的。通过设置明确的来源列表来解决。 这使我的 GET 请求成功,但我的 POST 发出了预检请求,并且我没有创建任何控制器操作来支持 OPTIONS 动词。为了解决这个问题,我将 MessageHandler 实现为全局 OPTIONS 处理程序。它只是为任何 OPTIONS 请求返回 200。我知道这并不完美,但目前在 Fiddler 中有效。

我被困在哪里 - 我的 Angular 预检调用不包括凭据。根据this answer,这是设计使然,因为 OPTIONS 请求被设计为匿名的。但是,Windows 身份验证使用 401 停止请求。

我尝试将 [AllowAnonymous] 属性放在我的 MessageHandler 上。在我的开发计算机上,它可以工作 - OPTIONS 动词不需要身份验证,但其他动词需要。但是,当我构建并部署到测试服务器时,我的 OPTIONS 请求继续收到 401。

在使用 Windows 身份验证时,是否可以在我的 MessageHandler 上应用 [AllowAnonymous]?如果是这样,有关如何执行此操作的任何指导?或者这是错误的兔子洞,我应该寻找不同的方法?

更新: 我能够通过在 IIS 中的站点上同时设置 Windows 身份验证和匿名身份验证来使其工作。这导致一切都允许匿名,所以我添加了一个全局过滤器 Authorize,同时在我的 MessageHandler 上保留 AllowAnonymous。

但是,这感觉像是一种 hack……我一直都明白,应该只使用一种身份验证方法(不能混合使用)。如果有人有更好的方法,我将不胜感激。

【问题讨论】:

您可能应该添加诸如“selfhost”或“owin”之类的标签,因为这与 IIS 之类的东西无关。 :) 我使用了本指南codeproject.com/Articles/1119206/…(2016 年发布),与下面的大多数答案非常相似 【参考方案1】:

我通过 HttpListener 使用自托管,以下解决方案对我有用:

    我允许匿名 OPTIONS 请求 在 SupportsCredentials 设置为 true 的情况下启用 CORS
var cors = new EnableCorsAttribute("*", "*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);
var listener = appBuilder.Properties["System.Net.HttpListener"] as HttpListener;
if (listener != null)

    listener.AuthenticationSchemeSelectorDelegate = (request) => 
    if (String.Compare(request.HttpMethod, "OPTIONS", true) == 0)
    
        return AuthenticationSchemes.Anonymous;
    
    else
    
        return AuthenticationSchemes.IntegratedWindowsAuthentication;
    ;

【讨论】:

这应该被标记为答案,AuthenticationSchemeSelectorDelegate 正是您需要确保 OPTIONS 请求被全局排除在任何身份验证设置之外。太棒了,这个答案也正是我所需要的。 (我建议将“selfhost”标签添加到问题中)。 如何在 webApiConfig.cs 注册方法中获取 appBuilder 实例? 这个答案的主要问题是问题直接提到他正在使用 IIS。 (我知道它可能是后来添加的,但仍然。更新了问题标签...)【参考方案2】:

我一直在努力使 CORS 请求在以下限制内工作(与 OP 的非常相似):

所有用户的 Windows 身份验证 不允许匿名身份验证 与 IE11 一起工作,in some cases 不发送 CORS 预检请求(或至少不作为 OPTIONS 请求到达 global.asax BeginRequest)

我的最终配置如下:

web.config - 允许未经身份验证(匿名)的预检请求 (OPTIONS)

<system.web>
    <authentication mode="Windows" />
    <authorization>
        <allow verbs="OPTIONS" users="*"/>
        <deny users="?" />
    </authorization>
</system.web>

global.asax.cs - 正确回复允许来自另一个域的调用者接收数据的标头

protected void Application_AuthenticateRequest(object sender, EventArgs e)

    if (Context.Request.HttpMethod == "OPTIONS")
    
        if (Context.Request.Headers["Origin"] != null)
            Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);

        Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, MaxDataServiceVersion");
        Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        Context.Response.AddHeader("Access-Control-Allow-Credentials", "true");

        Response.End();
    

CORS 启用

public static class WebApiConfig

    public static void Register(HttpConfiguration config)
    
        // all requests are enabled in this example. SupportsCredentials must be here to allow authenticated requests          
        var corsAttr = new EnableCorsAttribute("*", "*", "*")  SupportsCredentials = true ;
        config.EnableCors(corsAttr);
    


protected void Application_Start()

    GlobalConfiguration.Configure(WebApiConfig.Register);

【讨论】:

这有帮助!我最终没有将授权元素添加到 web.config 中,它仍然有效。我相信 Application_AuthenticateRequest 覆盖消除了在调用 OPTION 方法时允许进行身份验证的需要。 @MadMoai - 我会在没有授权的情况下尝试,尽管我认为在 IIS 中仅启用 Windows 身份验证并且必须为未经身份验证的请求启用选项时需要它。谢谢。 根据Microsoft,CORS 规范指出,如果 SupportsCredentials 为 true,则将来源设置为“*”是无效的。 @WorkSmarter 这是真的,但事实证明,WebAPI 2 (Microsoft.AspNet.WebApi.Cors.5.2.7) 的当前版本的 CORS 库不会使用此设置返回“Access-Control-Allow-Origin: *”,而是使用请求的 Origin 标头中的值...【参考方案3】:

这是一个更简单的解决方案——几行代码允许所有“OPTIONS”请求有效地模拟应用程序池帐户。您可以关闭匿名,并按照常规做法配置 CORS 策略,然后将以下内容添加到您的 global.asax.cs:

            protected void Application_AuthenticateRequest(object sender, EventArgs e)
            
                if (Context.Request.HttpMethod == "OPTIONS" && Context.User == null)
                
                    Context.User = System.Security.Principal.WindowsPrincipal.Current;
                
            

【讨论】:

【参考方案4】:

在我们的情况下:

Windows 身份验证 多个 CORS 来源 SupportCredentials 设置为 true IIS 托管

我们发现解决方案在别处:

Web.Config 中,我们只需添加 runAllManagedModulesForAllRequests=true

<modules runAllManagedModulesForAllRequests="true">

我们通过研究为什么没有触发 Application_BeginRequest 的解决方案最终得出了这个解决方案。

我们拥有的其他配置:

Web.Config

    <authentication mode="Windows" />
    <authorization>
      <allow verbs="OPTIONS" users="*" />
      <deny users="?"/>
    </authorization>

WebApiConfig

        private static string GetAllowedOrigins()
        
            return ConfigurationManager.AppSettings["CorsOriginsKey"];
        

        public static void Register(HttpConfiguration config)
        
            //set cors origins
            string origins = GetAllowedOrigins();
            var cors = new EnableCorsAttribute(origins, "*", "*");
            config.EnableCors(cors);

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/controller/action/id",
                defaults: new  id = RouteParameter.Optional 
            );
       

BTW "*" cors origin 与 Windows Authentication / SupportCredentials = true 不兼容

https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api#pass-credentials-in-cross-origin-requests

【讨论】:

【参考方案5】:

我以非常相似的方式解决了它,但有一些细节并专注于 oData 服务

我没有在 IIS 中禁用匿名身份验证,因为我需要它来发布请求

我在 Global.aspx 中添加了与上面相同的代码(在 Access-Control-Allow-Headers 中添加 MaxDataServiceVersion

protected void Application_BeginRequest(object sender, EventArgs e)

    if ((Context.Request.Path.Contains("api/") || Context.Request.Path.Contains("odata/")) && Context.Request.HttpMethod == "OPTIONS")
    
        Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
        Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,MaxDataServiceVersion");
        Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        Context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
        Context.Response.End();
    
 

WebAPIConfig.cs

public static void Register(HttpConfiguration config)

   // Web API configuration and services
   var cors = new EnableCorsAttribute("*", "*", "*");
   cors.SupportsCredentials = true;
   config.EnableCors(cors);


   config.Routes.MapHttpRoute(
       name: "DefaultApi",
       routeTemplate: "api/controller/id",
       defaults: new  id = RouteParameter.Optional 
   );

和 AngularJS 调用

$http(
       method: 'POST',
        url: 'http://XX.XXX.XXX.XX/oData/myoDataWS.svc/entityName',
        withCredentials: true,
        headers: 
            'Content-Type': 'application/json;odata=verbose',
            'Accept': 'application/json;odata=light;q=1,application/json;odata=verbose;q=0.5',
            'MaxDataServiceVersion': '3.0'
        ,
        data: 
            '@odata.type':'entityName',
            'field1': 1560,
            'field2': 24,
            'field3': 'sjhdjshdjsd',
            'field4':'wewewew',
            'field5':'ewewewe',
            'lastModifiedDate':'2015-10-26T11:45:00',
            'field6':'1359',
            'field7':'5'
        
    );

【讨论】:

【参考方案6】:

戴夫,

在玩弄了 CORS 包之后,这就是它对我有用的原因:[EnableCors(origins: "", headers: "", methods: "*", SupportsCredentials=true)]

我必须启用 SupportsCredentials=true。 Origins、Headers 和 Methods 都设置为“*”

【讨论】:

我从一开始就有这个设置,否则服务器不会接受凭据头。不过,感谢您的尝试。 asp.net/web-api/overview/security/… 部分:在跨域请求中传递凭据 CORS 规范还规定,如果 SupportsCredentials 为 true,则将源设置为“”是无效的。您应该提供您想要允许的来源,而不是使用“【参考方案7】:

如果不需要,请在 IIS 中禁用匿名身份验证。

在你的全局 asax 中添加这个:

protected void Application_BeginRequest(object sender, EventArgs e)

    if ((Context.Request.Path.Contains("api/") || Context.Request.Path.Contains("odata/")) && Context.Request.HttpMethod == "OPTIONS")
    
        Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
        Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        Context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
        Context.Response.End();
    
 

确保您在启用 cors 的位置也启用了凭证使用,例如:

public static void Register(HttpConfiguration config)

   // Web API configuration and services
   var cors = new EnableCorsAttribute("*", "*", "*");
   cors.SupportsCredentials = true;
   config.EnableCors(cors);

   // Web API routes
   config.MapHttpAttributeRoutes();

   config.Routes.MapHttpRoute(
       name: "DefaultApi",
       routeTemplate: "api/controller/id",
       defaults: new  id = RouteParameter.Optional 
   );

如您所见,我在全局范围内启用了 CORS,并使用应用程序 BeginRequest 挂钩对 api (Web Api) 的所有 OPTIONS 请求和 odata 请求(如果您使用它)进行身份验证。

这适用于所有浏览器,在客户端记得添加 xhrFiled withCredentials,如下所示。

$.ajax(
    type : method,
    url : apiUrl,
    dataType : "json",
    xhrFields: 
        withCredentials: true
    ,
    async : true,
    crossDomain : true,
    contentType : "application/json",
    data: data ? JSON.stringify(data) : ''
).....

我正在尝试寻找另一种避免使用钩子的解决方案,但直到现在都没有成功, 我会使用 web.config 配置来执行以下操作: 警告下面的配置不起作用!

  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <authentication mode="Windows" />
    <authorization>
      <deny verbs="GET,PUT,POST" users="?" />
      <allow verbs="OPTIONS" users="?"/>
    </authorization>
  </system.web>
  <location path="api">
    <system.web>
      <authorization>
        <allow users="?"/>
      </authorization>
    </system.web>
  </location>

【讨论】:

【参考方案8】:

我在网上找到的其他解决方案对我不起作用或看起来太老套了;最后我想出了一个更简单且可行的解决方案:

web.config:

<system.web>
    ...
    <authentication mode="Windows" />
    <authorization>
        <deny users="?" />
    </authorization>
</system.web>

项目属性:

    开启Windows Authentication 关闭Anonymous Authentication

设置 CORS:

[assembly: OwinStartup(typeof(Startup))]
namespace MyWebsite

    public class Startup
    
        public void Configuration(IAppBuilder app)
        
            app.UseCors(CorsOptions.AllowAll);

这需要 NUget 上可用的 Microsoft.Owin.Cors 程序集。

角度初始化:

$httpProvider.defaults.withCredentials = true;

【讨论】:

【参考方案9】:

这是我的解决方案。

Global.asax*

protected void Application_BeginRequest(object sender, EventArgs e)

    if(!ListOfAuthorizedOrigins.Contains(Context.Request.Headers["Origin"])) return;

    if (Request.HttpMethod == "OPTIONS")
    
        HttpContext.Current.Response.Headers.Remove("Access-Control-Allow-Origin");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
        HttpContext.Current.Response.StatusCode = 200;
        HttpContext.Current.Response.End();
    

    if (Request.Headers.AllKeys.Contains("Origin"))
    
        HttpContext.Current.Response.Headers.Remove("Access-Control-Allow-Origin");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
    

【讨论】:

以上是关于带有 Windows 身份验证的 WebAPI CORS - 允许匿名 OPTIONS 请求的主要内容,如果未能解决你的问题,请参考以下文章

Angular 7 .net 核心 2.2 WebApi Windows 身份验证 CORS

使用 Windows 身份验证在 Web API 控制器中获取 NetworkCredential 或客户端凭据

通过 Angular 调用时,Windows 身份验证不适用于 WebAPI

.Net Core WebAPI CORS 与 Windows 身份验证

带有 SPA 的 .NET Core WebApi 项目中的混合身份验证流程

使用 Windows Auth NTLM 进行身份验证