wcf - 具有 WCF 和 Windows 身份验证的 CORS

标签 wcf windows-authentication cors

是否可以在执行 Windows 身份验证的同时处理 WCF 服务的“跨源资源共享”请求?

我的场景:

  1. 我已经设置了一个通过 webHttpBinding 公开的自托管 WCF 服务。

  2. 应该使用 jQuery 从浏览器直接调用此服务。实际上,这会限制我使用 basicHttpBinding 或 webHttpBinding。在这种情况下,我使用 webHttpBinding 来调用服务操作。

  3. HTML 页面(将调用 WCF 服务)由与 WCF 服务位于同一台计算机但不同端口的网络服务器提供。这意味着我需要 CORS 支持才能使其在 Firefox、Chrome 中正常工作...

  4. 调用 WCF 服务时,用户必须使用 Windows 身份验证进行身份验证。为此,我将我的 webHttpBinding 配置为使用传输安全模式“TransportCredentialsOnly”。

W3C 规定在这种情况下应使用 CORS。 简单的说,这意味着浏览器会检测到我在做跨域请求。在实际向我的 WCF 服务发送请求之前,它会向我的 WCF 服务 URL 发送一个所谓的“预检”请求。此预检请求使用 HTTP 方法“OPTIONS”并询问是否允许原始 URL(= 为我的 HTML 提供服务的网络服务器)将请求发送到我的服务 URL。然后,浏览器在将实际请求发送到我的 WCF 服务之前需要 HTTP 200 响应(=“OK”)。来 self 的服务的任何其他回复将阻止发送实际请求。

此时 CORS 并未内置到 WCF 中,因此我使用 WCF 扩展点来添加 CORS 兼容性。

我的自托管服务的 App.Config 的服务部分:

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="MyApp.DefaultServiceBehavior">
        <serviceMetadata httpGetEnabled="True"/>
        <serviceDebug includeExceptionDetailInFaults="True"/>
      </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
      <behavior name="MyApp.DefaultEndpointBehavior">
        <webHttp/>
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <bindings>
    <webHttpBinding>
      <binding name="MyApp.DefaultWebHttpBinding">
        <security mode="TransportCredentialOnly">
          <transport clientCredentialType="Windows"/>
        </security>
      </binding>
    </webHttpBinding>
  </bindings>
  <services>
    <service 
      name="MyApp.FacadeLayer.LookupFacade"
      behaviorConfiguration="MyApp.DefaultServiceBehavior"
      >
      <endpoint
        contract="MyApp.Services.ILookupService"
        binding="webHttpBinding"
        bindingConfiguration="MyApp.DefaultWebHttpBinding"
        address=""
        behaviorConfiguration="MyApp.DefaultEndpointBehavior"
        >
      </endpoint>
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost/Temporary_Listen_Addresses/myapp/LookupService"/>
        </baseAddresses>
      </host>
    </service>
  </services>
</system.serviceModel>

我已经实现了一个 IDispatchMessageInspector 来回复预检消息:

public class CORSSupport : IDispatchMessageInspector
{
    private Dictionary<string, string> requiredHeaders;

    public CORSSupport(Dictionary<string, string> requiredHeaders)
    {
        this.requiredHeaders = requiredHeaders ?? new Dictionary<string, string>();
    }

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        HttpRequestMessageProperty httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty;

        if (httpRequest.Method.ToUpper() == "OPTIONS")
            instanceContext.Abort();

        return httpRequest;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        HttpRequestMessageProperty httpRequest = correlationState as HttpRequestMessageProperty;
        HttpResponseMessageProperty httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty;

        foreach (KeyValuePair<string, string> item in this.requiredHeaders)
            httpResponse.Headers.Add(item.Key, item.Value);

        string origin = httpRequest.Headers["origin"];
        if (origin != null)
            httpResponse.Headers.Add("Access-Control-Allow-Origin", origin);

        if (httpRequest.Method.ToUpper() == "OPTIONS")
            httpResponse.StatusCode = HttpStatusCode.NoContent;
    }
}

此 IDispatchMessageInspector 通过自定义 IServiceBehavior 属性注册。

我像这样通过 jQuery 调用我的服务:

$.ajax(
    {
        url: 'http://localhost/Temporary_Listen_Addresses/myapp/LookupService/SomeLookup',
        type: 'GET',
        xhrFields:
            {
                withCredentials: true
            }
    }
)
.done(function () { alert('Yay!'); })
.error(function () { alert('Nay!'); });

这适用于 IE10 和 Chrome(我收到一个消息框说“耶!”),但不适用于 Firefox。在 Firefox 中,我得到一个“不!”和 HTTP 401(未经授权)错误。

此 401 是由于我在服务配置中设置的“Windows 身份验证”造成的。身份验证的工作方式是浏览器首先发送一个没有任何身份验证信息的请求。然后服务器回复 HTTP 401(未授权)指示要使用的身份验证方法。然后,浏览器通常会重新提交包含用户凭据的请求(之后请求将正常进行)。

不幸的是,W3C 似乎已经指出不应将凭据传递到 CORS 预检消息中。因此,WCF 使用 HTTP 401 进行回复。似乎 Chrome 确实以某种方式在预检请求 header 中发送了凭据(根据 W3C 规范,这实际上是不正确的),而 Firefox 则没有。 此外,W3C 仅识别对预检请求的 HTTP 200 响应:任何其他响应(例如我收到的 HTTP 401)仅表示 CORS 请求失败,实际请求可能不会提交...

我不知道如何让这个(简单的)场景发挥作用。谁能帮忙?

最佳答案

更进一步。

使用 .NET 4.5,可以为单个端点支持多种身份验证方案。这允许我同时定义 Windows 身份验证和匿名身份验证:

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="MyApp.DefaultServiceBehavior">
        <serviceMetadata httpGetEnabled="True"/>
        <serviceDebug includeExceptionDetailInFaults="True"/>
        <serviceAuthenticationManager authenticationSchemes="Negotiate, Anonymous"/>
      </behavior>
    </serviceBehaviors>
    <endpointBehaviors>
      <behavior name="MyApp.DefaultEndpointBehavior">
        <webHttp/>
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <bindings>
    <webHttpBinding>
      <binding name="MyApp.DefaultWebHttpBinding">
        <security mode="TransportCredentialOnly">
          <transport clientCredentialType="InheritedFromHost"/>
        </security>
      </binding>
    </webHttpBinding>
  </bindings>
  <services>
    <service 
      name="MyApp.FacadeLayer.LookupFacade"
      behaviorConfiguration="MyApp.DefaultServiceBehavior"
      >
      <endpoint
        contract="MyApp.Services.ILookupService"
        binding="webHttpBinding"
        bindingConfiguration="MyApp.DefaultWebHttpBinding"
        address=""
        behaviorConfiguration="MyApp.DefaultEndpointBehavior"
        >
      </endpoint>
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost/Temporary_Listen_Addresses/myapp/LookupService"/>
        </baseAddresses>
      </host>
    </service>
  </services>
</system.serviceModel>

这样,我的 IDispatchMessageInspector 就会被调用,我可以正确处理所有浏览器的预检消息。

然后我想调整我的 IDispatchMessageInspector 以对预检以外的任何请求强制执行身份验证:

public class CrossOriginResourceSharingMessageInspector : IDispatchMessageInspector
{
    private Dictionary<string, string> requiredHeaders;

    public CrossOriginResourceSharingMessageInspector(Dictionary<string, string> requiredHeaders)
    {
        this.requiredHeaders = requiredHeaders ?? new Dictionary<string, string>();
    }

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        HttpRequestMessageProperty httpRequestHeader = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;

        if (httpRequestHeader.Method.ToUpper() == "OPTIONS")
            instanceContext.Abort();

        else if (httpRequestHeader.Headers[HttpRequestHeader.Authorization] == null)
            instanceContext.Abort();

        return httpRequestHeader;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        HttpRequestMessageProperty httpRequestHeader = correlationState as HttpRequestMessageProperty;
        HttpResponseMessageProperty httpResponseHeader = reply.Properties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;

        foreach (KeyValuePair<string, string> item in this.requiredHeaders)
            httpResponseHeader.Headers.Add(item.Key, item.Value);

        string origin = httpRequestHeader.Headers["origin"];
        if (origin != null)
            httpResponseHeader.Headers.Add("Access-Control-Allow-Origin", origin);

        string method = httpRequestHeader.Method;
        if (method.ToUpper() == "OPTIONS")
        {
            httpResponseHeader.StatusCode = HttpStatusCode.NoContent;
        }

        else if (httpRequestHeader.Headers[HttpRequestHeader.Authorization] == null)
        {
            httpResponseHeader.StatusDescription = "Unauthorized";
            httpResponseHeader.StatusCode = HttpStatusCode.Unauthorized;
        }
    }
}

同样,这似乎适用于 IE 和 Chrome,但不适用于 Firefox。 Preflight 现在可以用于 Firefox,但在实际请求不包含用户凭据时我用 HTTP 401 回复后,Firefox 似乎没有重新提交请求。事实上,我希望 Firefox 立即发送凭据和 GET 请求(看到我在我的 jQuery AJAX 请求中添加了“withCredentials: true”;不过 Chrome 似乎确实可以正确执行此操作)。

我做错了什么?

关于wcf - 具有 WCF 和 Windows 身份验证的 CORS,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19124229/

相关文章:

.net - 找不到配置绑定(bind)扩展

cors - 如何允许 OWIN token 请求使用特定的 CORS 来源、 header 和方法?

php - 带有 Auth 的 Yii2 CORS 不适用于非 CRUD 操作

javascript - Canvas 被跨源数据污染

c# - 需要对后台工作人员的建议

c# - 异步 WCF REST 服务中跨线程的上下文

SQL Server 2008 错误 18452 登录来自不受信任的域,无法与 Windows 身份验证一起使用

angular cli + windows 身份验证后端

.net - 模拟 WCF 服务的简单方法?

macos - Jetbrains 0xDBe : Connecting to SQL Server using Windows Authentication?